diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 8ab39e50cf..b83fb1abaa 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -8,6 +8,7 @@ use frame_support::{ }; use frame_system::{self as system, offchain::CreateTransactionBase}; use frame_system::{EnsureNever, EnsureRoot, limits}; +use pallet_subtensor::EvmOriginHelper; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_consensus_grandpa::AuthorityList as GrandpaAuthorityList; use sp_core::U256; @@ -28,7 +29,7 @@ frame_support::construct_runtime!( System: frame_system = 1, Balances: pallet_balances = 2, AdminUtils: crate = 3, - SubtensorModule: pallet_subtensor::{Pallet, Call, Storage, Event, Error} = 4, + SubtensorModule: pallet_subtensor::{Pallet, Call, Storage, Event, Error, Origin} = 4, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 5, Drand: pallet_drand::{Pallet, Call, Storage, Event} = 6, Grandpa: pallet_grandpa = 7, @@ -151,8 +152,15 @@ parameter_types! { pub const LeaseDividendsDistributionInterval: u32 = 100; // 100 blocks } +impl EvmOriginHelper for () { + fn make_evm_origin(_: AccountId) -> RuntimeOrigin { + RuntimeOrigin::none() + } +} impl pallet_subtensor::Config for Test { + type EvmOriginHelper = (); type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type Currency = Balances; type InitialIssuance = InitialIssuance; @@ -396,6 +404,7 @@ impl pallet_drand::Config for Test { type Verifier = pallet_drand::verifier::QuicknetVerifier; type UnsignedPriority = ConstU64<{ 1 << 20 }>; type HttpFetchTimeout = ConstU64<1_000>; + type OnPulseReceived = (); } impl frame_system::offchain::SigningTypes for Test { diff --git a/pallets/commitments/src/mock.rs b/pallets/commitments/src/mock.rs index 19e0e726da..82c93789ba 100644 --- a/pallets/commitments/src/mock.rs +++ b/pallets/commitments/src/mock.rs @@ -123,6 +123,7 @@ impl pallet_drand::Config for Test { type Verifier = pallet_drand::verifier::QuicknetVerifier; type UnsignedPriority = ConstU64<{ 1 << 20 }>; type HttpFetchTimeout = ConstU64<1_000>; + type OnPulseReceived = (); } pub mod test_crypto { diff --git a/pallets/drand/src/lib.rs b/pallets/drand/src/lib.rs index d82f39581a..deab1cfb36 100644 --- a/pallets/drand/src/lib.rs +++ b/pallets/drand/src/lib.rs @@ -146,6 +146,24 @@ impl SignedPayload for PulsesPayload OnPulseReceived for (X, Y) { + fn on_pulse_received(round: RoundNumber) { + X::on_pulse_received(round); + Y::on_pulse_received(round); + } +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -174,6 +192,9 @@ pub mod pallet { /// complete. #[pallet::constant] type HttpFetchTimeout: Get; + /// + /// Warning: calculate weight properly when change this parameter + type OnPulseReceived: OnPulseReceived; } /// the drand beacon configuration @@ -352,6 +373,7 @@ pub mod pallet { // Emit event with all new rounds if !new_rounds.is_empty() { Self::deposit_event(Event::NewPulse { rounds: new_rounds }); + T::OnPulseReceived::on_pulse_received(last_stored_round); } Ok(()) diff --git a/pallets/drand/src/mock.rs b/pallets/drand/src/mock.rs index f8c0d9ab4d..53c1019404 100644 --- a/pallets/drand/src/mock.rs +++ b/pallets/drand/src/mock.rs @@ -102,6 +102,7 @@ impl pallet_drand_bridge::Config for Test { type Verifier = QuicknetVerifier; type UnsignedPriority = UnsignedPriority; type HttpFetchTimeout = ConstU64<1_000>; + type OnPulseReceived = (); } // Build genesis storage according to the mock runtime. diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index b239adb70e..4a8dd502a0 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -508,7 +508,7 @@ mod pallet_benchmarks { let block_number: u64 = Subtensor::::get_current_block_as_u64(); let (nonce, work) = Subtensor::::create_work_for_block_number(netuid, block_number, 3, &hotkey); - let origin = T::RuntimeOrigin::from(RawOrigin::Signed(hotkey.clone())); + let origin = OriginFor::::from(RawOrigin::Signed(hotkey.clone())); assert_ok!(Subtensor::::register( origin.clone(), netuid, @@ -1598,4 +1598,469 @@ mod pallet_benchmarks { assert_eq!(TokenSymbol::::get(netuid), new_symbol); } + + #[benchmark] + fn add_stake_aggregate() { + let netuid: NetUid = 1u16.into(); + let tempo: u16 = 1; + let seed: u32 = 1; + + Subtensor::::init_new_network(netuid, tempo); + Subtensor::::set_burn(netuid, 1); + Subtensor::::set_network_registration_allowed(netuid, true); + SubtokenEnabled::::insert(netuid, true); + Subtensor::::set_max_allowed_uids(netuid, 4096); + assert_eq!(Subtensor::::get_max_allowed_uids(netuid), 4096); + + let coldkey: T::AccountId = account("Test", 0, seed); + let hotkey: T::AccountId = account("Alice", 0, seed); + + let amount: u64 = 6000000; + let amount_to_be_staked = 1000000000u64; + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount_to_be_staked); + + assert_ok!(Subtensor::::do_burned_registration( + RawOrigin::Signed(coldkey.clone()).into(), + netuid, + hotkey.clone() + )); + + #[extrinsic_call] + _(RawOrigin::Signed(coldkey.clone()), hotkey, netuid, amount); + } + + #[benchmark] + fn add_stake_limit_aggregate() { + let netuid: NetUid = 1u16.into(); + let tempo: u16 = 1; + let seed: u32 = 1; + + Subtensor::::init_new_network(netuid, tempo); + Subtensor::::set_burn(netuid, 1); + Subtensor::::set_network_registration_allowed(netuid, true); + Subtensor::::set_max_allowed_uids(netuid, 4096); + SubtokenEnabled::::insert(netuid, true); + + let coldkey: T::AccountId = account("Test", 0, seed); + let hotkey: T::AccountId = account("Alice", 0, seed); + + let amount = 900_000_000_000; + let limit: u64 = 6_000_000_000; + let amount_to_be_staked = 440_000_000_000; + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount); + + let tao_reserve = 150_000_000_000_u64; + let alpha_in: AlphaCurrency = 100_000_000_000_u64.into(); + SubnetTAO::::insert(netuid, tao_reserve); + SubnetAlphaIn::::insert(netuid, alpha_in); + + assert_ok!(Subtensor::::do_burned_registration( + RawOrigin::Signed(coldkey.clone()).into(), + netuid, + hotkey.clone() + )); + + #[extrinsic_call] + _( + RawOrigin::Signed(coldkey.clone()), + hotkey, + netuid, + amount_to_be_staked, + limit, + false, + ) + } + + #[benchmark] + fn remove_stake_limit_aggregate() { + let netuid: NetUid = 1u16.into(); + let tempo: u16 = 1; + let seed: u32 = 1; + + // Set our total stake to 1000 TAO + Subtensor::::increase_total_stake(1_000_000_000_000); + + Subtensor::::init_new_network(netuid, tempo); + Subtensor::::set_network_registration_allowed(netuid, true); + SubtokenEnabled::::insert(netuid, true); + Subtensor::::set_max_allowed_uids(netuid, 4096); + assert_eq!(Subtensor::::get_max_allowed_uids(netuid), 4096); + + let coldkey: T::AccountId = account("Test", 0, seed); + let hotkey: T::AccountId = account("Alice", 0, seed); + Subtensor::::set_burn(netuid, 1); + + let limit: u64 = 1_000_000_000; + let tao_reserve = 150_000_000_000_u64; + let alpha_in: AlphaCurrency = 100_000_000_000_u64.into(); + SubnetTAO::::insert(netuid, tao_reserve); + SubnetAlphaIn::::insert(netuid, alpha_in); + + let wallet_bal = 1000000u32.into(); + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), wallet_bal); + + assert_ok!(Subtensor::::do_burned_registration( + RawOrigin::Signed(coldkey.clone()).into(), + netuid, + hotkey.clone() + )); + + let u64_staked_amt = 100_000_000_000; + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), u64_staked_amt); + + assert_ok!(Subtensor::::add_stake( + RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + u64_staked_amt + )); + + let amount_unstaked: u64 = 30_000_000_000; + + // Remove stake limit for benchmark + StakingOperationRateLimiter::::remove((hotkey.clone(), coldkey.clone(), netuid)); + + #[extrinsic_call] + _( + RawOrigin::Signed(coldkey.clone()), + hotkey.clone(), + netuid, + amount_unstaked.into(), + limit, + false, + ) + } + + #[benchmark] + fn remove_stake_aggregate() { + let netuid: NetUid = 1u16.into(); + let tempo: u16 = 1; + let seed: u32 = 1; + + // Set our total stake to 1000 TAO + Subtensor::::increase_total_stake(1_000_000_000_000); + + Subtensor::::init_new_network(netuid, tempo); + Subtensor::::set_network_registration_allowed(netuid, true); + SubtokenEnabled::::insert(netuid, true); + Subtensor::::set_max_allowed_uids(netuid, 4096); + assert_eq!(Subtensor::::get_max_allowed_uids(netuid), 4096); + + let coldkey: T::AccountId = account("Test", 0, seed); + let hotkey: T::AccountId = account("Alice", 0, seed); + Subtensor::::set_burn(netuid, 1); + + let wallet_bal = 1000000u32.into(); + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), wallet_bal); + + assert_ok!(Subtensor::::do_burned_registration( + RawOrigin::Signed(coldkey.clone()).into(), + netuid, + hotkey.clone() + )); + + // Stake 10% of our current total staked TAO + let u64_staked_amt = 100_000_000_000; + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), u64_staked_amt); + + assert_ok!(Subtensor::::add_stake( + RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + u64_staked_amt + )); + + let amount_unstaked: u64 = 600000; + + // Remove stake limit for benchmark + StakingOperationRateLimiter::::remove((hotkey.clone(), coldkey.clone(), netuid)); + + #[extrinsic_call] + _( + RawOrigin::Signed(coldkey.clone()), + hotkey.clone(), + netuid, + amount_unstaked.into(), + ) + } + + #[benchmark] + fn remove_stake_full_limit_aggregate() { + let netuid = NetUid::from(1); + let tempo: u16 = 1; + let seed: u32 = 1; + + // Set our total stake to 1000 TAO + Subtensor::::increase_total_stake(1_000_000_000_000); + + Subtensor::::init_new_network(netuid, tempo); + Subtensor::::set_network_registration_allowed(netuid, true); + SubtokenEnabled::::insert(netuid, true); + + Subtensor::::set_max_allowed_uids(netuid, 4096); + assert_eq!(Subtensor::::get_max_allowed_uids(netuid), 4096); + + let coldkey: T::AccountId = account("Test", 0, seed); + let hotkey: T::AccountId = account("Alice", 0, seed); + Subtensor::::set_burn(netuid, 1); + + let limit: u64 = 1_000_000_000; + let tao_reserve = 150_000_000_000_u64; + let alpha_in = AlphaCurrency::from(100_000_000_000); + SubnetTAO::::insert(netuid, tao_reserve); + SubnetAlphaIn::::insert(netuid, alpha_in); + + let wallet_bal = 1000000u32.into(); + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), wallet_bal); + + assert_ok!(Subtensor::::do_burned_registration( + RawOrigin::Signed(coldkey.clone()).into(), + netuid, + hotkey.clone() + )); + + let u64_staked_amt = 100_000_000_000; + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), u64_staked_amt); + + assert_ok!(Subtensor::::add_stake( + RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + u64_staked_amt + )); + + StakingOperationRateLimiter::::remove((hotkey.clone(), coldkey.clone(), netuid)); + + #[extrinsic_call] + _( + RawOrigin::Signed(coldkey.clone()), + hotkey.clone(), + netuid, + Some(limit), + ); + } + + #[benchmark] + fn move_stake_aggregate() { + let coldkey: T::AccountId = whitelisted_caller(); + let origin: T::AccountId = account("A", 0, 1); + let destination: T::AccountId = account("B", 0, 2); + let netuid = NetUid::from(1); + + SubtokenEnabled::::insert(netuid, true); + Subtensor::::init_new_network(netuid, 1); + + let burn_fee = Subtensor::::get_burn_as_u64(netuid); + let stake_tao: u64 = DefaultMinStake::::get().saturating_mul(10); + let deposit = burn_fee.saturating_mul(2).saturating_add(stake_tao); + Subtensor::::add_balance_to_coldkey_account(&coldkey, deposit); + + assert_ok!(Subtensor::::burned_register( + RawOrigin::Signed(coldkey.clone()).into(), + netuid, + origin.clone() + )); + + SubnetTAO::::insert(netuid, deposit); + SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(deposit)); + TotalStake::::set(deposit); + + assert_ok!(Subtensor::::add_stake_limit( + RawOrigin::Signed(coldkey.clone()).into(), + origin.clone(), + netuid, + stake_tao, + u64::MAX, + false + )); + + let alpha_to_move = + Subtensor::::get_stake_for_hotkey_and_coldkey_on_subnet(&origin, &coldkey, netuid); + + Subtensor::::create_account_if_non_existent(&coldkey, &destination); + + // Remove stake limit for benchmark + StakingOperationRateLimiter::::remove((origin.clone(), coldkey.clone(), netuid)); + + #[extrinsic_call] + _( + RawOrigin::Signed(coldkey.clone()), + origin.clone(), + destination.clone(), + netuid, + netuid, + alpha_to_move, + ); + } + + #[benchmark] + fn transfer_stake_aggregate() { + let coldkey: T::AccountId = whitelisted_caller(); + let dest: T::AccountId = account("B", 0, 2); + let hot: T::AccountId = account("A", 0, 1); + let netuid = NetUid::from(1); + + SubtokenEnabled::::insert(netuid, true); + Subtensor::::init_new_network(netuid, 1); + + let reg_fee = Subtensor::::get_burn_as_u64(netuid); + let stake_tao: u64 = DefaultMinStake::::get().saturating_mul(10); + let deposit = reg_fee.saturating_mul(2).saturating_add(stake_tao); + Subtensor::::add_balance_to_coldkey_account(&coldkey, deposit); + + assert_ok!(Subtensor::::burned_register( + RawOrigin::Signed(coldkey.clone()).into(), + netuid, + hot.clone() + )); + + SubnetTAO::::insert(netuid, deposit); + SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(deposit)); + TotalStake::::set(deposit); + + assert_ok!(Subtensor::::add_stake_limit( + RawOrigin::Signed(coldkey.clone()).into(), + hot.clone(), + netuid, + stake_tao, + u64::MAX, + false + )); + + let alpha_to_transfer = + Subtensor::::get_stake_for_hotkey_and_coldkey_on_subnet(&hot, &coldkey, netuid); + + Subtensor::::create_account_if_non_existent(&dest, &hot); + + // Remove stake limit for benchmark + StakingOperationRateLimiter::::remove((hot.clone(), coldkey.clone(), netuid)); + + #[extrinsic_call] + _( + RawOrigin::Signed(coldkey.clone()), + dest.clone(), + hot.clone(), + netuid, + netuid, + alpha_to_transfer, + ); + } + + #[benchmark] + fn swap_stake_aggregate() { + let coldkey: T::AccountId = whitelisted_caller(); + let hot: T::AccountId = account("A", 0, 9); + let netuid1 = NetUid::from(1); + let netuid2 = NetUid::from(2); + + SubtokenEnabled::::insert(netuid1, true); + Subtensor::::init_new_network(netuid1, 1); + SubtokenEnabled::::insert(netuid2, true); + Subtensor::::init_new_network(netuid2, 1); + + let reg_fee = Subtensor::::get_burn_as_u64(netuid1); + let stake_tao: u64 = DefaultMinStake::::get().saturating_mul(10); + let deposit = reg_fee.saturating_mul(2).saturating_add(stake_tao); + Subtensor::::add_balance_to_coldkey_account(&coldkey, deposit); + + assert_ok!(Subtensor::::burned_register( + RawOrigin::Signed(coldkey.clone()).into(), + netuid1, + hot.clone() + )); + + SubnetTAO::::insert(netuid1, deposit); + SubnetAlphaIn::::insert(netuid1, AlphaCurrency::from(deposit)); + SubnetTAO::::insert(netuid2, deposit); + SubnetAlphaIn::::insert(netuid2, AlphaCurrency::from(deposit)); + TotalStake::::set(deposit); + + assert_ok!(Subtensor::::add_stake_limit( + RawOrigin::Signed(coldkey.clone()).into(), + hot.clone(), + netuid1, + stake_tao, + u64::MAX, + false + )); + + let alpha_to_swap = + Subtensor::::get_stake_for_hotkey_and_coldkey_on_subnet(&hot, &coldkey, netuid1); + + // Remove stake limit for benchmark + StakingOperationRateLimiter::::remove((hot.clone(), coldkey.clone(), netuid1)); + + #[extrinsic_call] + _( + RawOrigin::Signed(coldkey.clone()), + hot.clone(), + netuid1, + netuid2, + alpha_to_swap, + ); + } + + #[benchmark] + fn swap_stake_limit_aggregate() { + let coldkey: T::AccountId = whitelisted_caller::>(); + let hot: T::AccountId = account("A", 0, 1); + let netuid1 = NetUid::from(1); + let netuid2 = NetUid::from(2); + let allow: bool = true; + + SubtokenEnabled::::insert(netuid1, true); + Subtensor::::init_new_network(netuid1, 1); + SubtokenEnabled::::insert(netuid2, true); + Subtensor::::init_new_network(netuid2, 1); + + let tao_reserve = 150_000_000_000_u64; + let alpha_in = AlphaCurrency::from(100_000_000_000); + SubnetTAO::::insert(netuid1, tao_reserve); + SubnetAlphaIn::::insert(netuid1, alpha_in); + SubnetTAO::::insert(netuid2, tao_reserve); + + Subtensor::::increase_total_stake(1_000_000_000_000); + + let amount = 900_000_000_000; + let limit_stake: u64 = 6_000_000_000; + let limit_swap: u64 = 1_000_000_000; + let amount_to_be_staked = 440_000_000_000; + let amount_swapped = AlphaCurrency::from(30_000_000_000); + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount); + + assert_ok!(Subtensor::::burned_register( + RawOrigin::Signed(coldkey.clone()).into(), + netuid1, + hot.clone() + )); + + assert_ok!(Subtensor::::burned_register( + RawOrigin::Signed(coldkey.clone()).into(), + netuid2, + hot.clone() + )); + + assert_ok!(Subtensor::::add_stake_limit( + RawOrigin::Signed(coldkey.clone()).into(), + hot.clone(), + netuid1, + amount_to_be_staked, + limit_stake, + allow + )); + + // Remove stake limit for benchmark + StakingOperationRateLimiter::::remove((hot.clone(), coldkey.clone(), netuid1)); + + #[extrinsic_call] + _( + RawOrigin::Signed(coldkey.clone()), + hot.clone(), + netuid1, + netuid2, + amount_swapped, + limit_swap, + allow, + ); + } } diff --git a/pallets/subtensor/src/coinbase/reveal_commits.rs b/pallets/subtensor/src/coinbase/reveal_commits.rs index 9318321633..fd1fa06bdb 100644 --- a/pallets/subtensor/src/coinbase/reveal_commits.rs +++ b/pallets/subtensor/src/coinbase/reveal_commits.rs @@ -133,7 +133,7 @@ impl Pallet { }; if let Err(e) = Self::do_set_weights( - T::RuntimeOrigin::signed(who.clone()), + OriginFor::::signed(who.clone()), netuid, payload.uids, payload.values, diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 279132e938..b30e882eef 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -82,7 +82,7 @@ impl Pallet { /// # Returns: /// * 'DispatchResult': A result type indicating success or failure of the registration. /// - pub fn do_root_register(origin: T::RuntimeOrigin, hotkey: T::AccountId) -> DispatchResult { + pub fn do_root_register(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { // --- 0. Get the unique identifier (UID) for the root network. let current_block_number: u64 = Self::get_current_block_as_u64(); ensure!( @@ -218,7 +218,7 @@ impl Pallet { // # Returns: // * 'DispatchResult': A result type indicating success or failure of the registration. // - pub fn do_adjust_senate(origin: T::RuntimeOrigin, hotkey: T::AccountId) -> DispatchResult { + pub fn do_adjust_senate(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { ensure!( Self::if_subnet_exist(NetUid::ROOT), Error::::RootNetworkDoesNotExist @@ -325,7 +325,7 @@ impl Pallet { } pub fn do_vote_root( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: &T::AccountId, proposal: T::Hash, index: u32, @@ -373,7 +373,7 @@ impl Pallet { /// Facilitates the removal of a user's subnetwork. /// /// # Args: - /// * 'origin': ('T::RuntimeOrigin'): The calling origin. Must be signed. + /// * 'origin': ('OriginFor'): The calling origin. Must be signed. /// * 'netuid': ('u16'): The unique identifier of the network to be removed. /// /// # Event: diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index c536ffff82..4933e76f6b 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -1375,7 +1375,7 @@ impl Pallet { } pub fn do_set_alpha_values( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, alpha_low: u16, alpha_high: u16, diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index e61aae04e5..f8ea854b57 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -19,10 +19,13 @@ use frame_support::{ use codec::{Decode, DecodeWithMemTracking, Encode}; use frame_support::sp_runtime::transaction_validity::InvalidTransaction; use frame_support::sp_runtime::transaction_validity::ValidTransaction; +use frame_support::traits::OriginTrait; +use frame_system::pallet_prelude::OriginFor; use pallet_balances::Call as BalancesCall; // use pallet_scheduler as Scheduler; use scale_info::TypeInfo; use sp_core::Get; +use sp_runtime::traits::BadOrigin; use sp_runtime::{ DispatchError, traits::{ @@ -62,6 +65,23 @@ extern crate alloc; pub const MAX_CRV3_COMMIT_SIZE_BYTES: u32 = 5000; +pub fn ensure_evm_origin(o: OuterOrigin) -> Result +where + OuterOrigin: Into, OuterOrigin>>, +{ + match o.into() { + Ok(Origin::::Evm { account_id }) => Ok(account_id), + _ => Err(BadOrigin), + } +} + +pub trait EvmOriginHelper +where + O: OriginTrait, +{ + fn make_evm_origin(account_id: A) -> O; +} + #[deny(missing_docs)] #[import_section(errors::errors)] #[import_section(events::events)] @@ -71,10 +91,12 @@ pub const MAX_CRV3_COMMIT_SIZE_BYTES: u32 = 5000; #[import_section(config::config)] #[frame_support::pallet] pub mod pallet { + use crate::EvmOriginHelper; use crate::RateLimitKey; use crate::migrations; use crate::subnets::leasing::{LeaseId, SubnetLeaseOf}; use frame_support::Twox64Concat; + use frame_support::dispatch::RawOrigin; use frame_support::{ BoundedVec, dispatch::GetDispatchInfo, @@ -99,6 +121,17 @@ pub mod pallet { #[cfg(feature = "std")] use sp_std::prelude::Box; + /// Origin for the EVM transactions. + #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, RuntimeDebug, MaxEncodedLen)] + #[pallet::origin] + pub enum Origin { + /// EVM origin. + Evm { + /// Account ID + account_id: T::AccountId, + }, + } + /// Origin for the pallet pub type PalletsOriginOf = <::RuntimeOrigin as OriginTrait>::PalletsOrigin; @@ -302,6 +335,158 @@ pub mod pallet { pub additional: Vec, } + /// Data structure for stake related jobs. + #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, Debug)] + pub enum StakeJob { + /// Represents a job for "add_stake" operation + AddStake { + /// Hotkey account + hotkey: AccountId, + /// Coldkey account + coldkey: AccountId, + /// Subnet ID + netuid: NetUid, + /// The amount of stake to be added to the hotkey staking account. + stake_to_be_added: u64, + }, + /// Represents a job for "remove_stake" operation + RemoveStake { + /// Hotkey account + hotkey: AccountId, + /// Coldkey account + coldkey: AccountId, + /// Subnet ID + netuid: NetUid, + /// Alpha value + alpha_unstaked: AlphaCurrency, + }, + /// Represents a job for "add_stake_limit" operation + AddStakeLimit { + /// Coldkey account + coldkey: AccountId, + /// Hotkey account + hotkey: AccountId, + /// Subnet ID + netuid: NetUid, + /// The amount of stake to be added to the hotkey staking account. + stake_to_be_added: u64, + /// The limit price expressed in units of RAO per one Alpha. + limit_price: u64, + /// Allows partial execution of the amount. If set to false, this becomes + /// fill or kill type or order. + allow_partial: bool, + }, + /// Represents a job for "remove_stake_limit" operation + RemoveStakeLimit { + /// Coldkey account + coldkey: AccountId, + /// Hotkey account + hotkey: AccountId, + /// Subnet ID + netuid: NetUid, + /// The amount of stake to be added to the hotkey staking account. + alpha_unstaked: AlphaCurrency, + /// The limit price + limit_price: u64, + /// Allows partial execution of the amount. If set to false, this becomes + /// fill or kill type or order. + allow_partial: bool, + }, + /// Represents a job for "unstake_all" operation + UnstakeAll { + /// Coldkey account + coldkey: AccountId, + /// Hotkey account + hotkey: AccountId, + }, + /// Represents a job for "unstake_all_alpha" operation + UnstakeAllAlpha { + /// Coldkey account + coldkey: AccountId, + /// Hotkey account + hotkey: AccountId, + }, + /// Represents a job for "move_stake" operation + MoveStake { + /// Coldkey account + coldkey: AccountId, + /// Origin hotkey account + origin_hotkey: AccountId, + /// Destination hotkey account + destination_hotkey: AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + }, + /// Represents a job for "transfer_stake" operation + TransferStake { + /// Origin coldkey account + origin_coldkey: AccountId, + /// Destinatino coldkey account + destination_coldkey: AccountId, + /// Hotkey account + hotkey: AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + }, + /// Represents a job for "swap_stake" operation + SwapStake { + /// Coldkey account + coldkey: AccountId, + /// Hotkey account + hotkey: AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + }, + /// Represents a job for "swap_stake_limit" operation + SwapStakeLimit { + /// Coldkey account + coldkey: AccountId, + /// Hotkey account + hotkey: AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + /// The limit price + limit_price: u64, + /// Allows partial execution of the amount. If set to false, this becomes + /// fill or kill type or order. + allow_partial: bool, + }, + } + + impl StakeJob { + /// Extracts coldkey from the stake job + pub fn coldkey(&self) -> AccountId { + match self { + StakeJob::AddStake { coldkey, .. } => coldkey.clone(), + StakeJob::RemoveStake { coldkey, .. } => coldkey.clone(), + StakeJob::AddStakeLimit { coldkey, .. } => coldkey.clone(), + StakeJob::RemoveStakeLimit { coldkey, .. } => coldkey.clone(), + StakeJob::UnstakeAll { coldkey, .. } => coldkey.clone(), + StakeJob::UnstakeAllAlpha { coldkey, .. } => coldkey.clone(), + StakeJob::MoveStake { coldkey, .. } => coldkey.clone(), + StakeJob::TransferStake { origin_coldkey, .. } => origin_coldkey.clone(), + StakeJob::SwapStake { coldkey, .. } => coldkey.clone(), + StakeJob::SwapStakeLimit { coldkey, .. } => coldkey.clone(), + } + } + } + /// ============================ /// ==== Staking + Accounts ==== /// ============================ @@ -894,6 +1079,17 @@ pub mod pallet { DefaultZeroU64, >; + #[pallet::storage] + pub type StakeJobs = StorageDoubleMap< + _, + Blake2_128Concat, + BlockNumberFor, // first key: current block number + Twox64Concat, + u64, // second key: unique job ID + StakeJob, + OptionQuery, + >; + #[pallet::storage] /// Ensures unique IDs for StakeJobs storage map pub type NextStakeJobId = StorageValue<_, u64, ValueQuery, DefaultZeroU64>; diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 3479ad8101..5bd3871894 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -1,6 +1,7 @@ #![allow(clippy::crate_in_macro_def)] use frame_support::pallet_macros::pallet_section; + /// A [`pallet_section`] that defines the errors for a pallet. /// This can later be imported into the pallet using [`import_section`]. #[pallet_section] @@ -13,9 +14,16 @@ mod config { pub trait Config: frame_system::Config + pallet_drand::Config + pallet_crowdloan::Config { + /// Helps create EVM origin by account ID + type EvmOriginHelper: EvmOriginHelper<::RuntimeOrigin, Self::AccountId>; + + /// Custom runtime origin for EVM + type RuntimeOrigin: From<::RuntimeOrigin> + + Into, ::RuntimeOrigin>>; + /// call type type RuntimeCall: Parameter - + Dispatchable + + Dispatchable> + From> + IsType<::RuntimeCall> + From>; @@ -25,11 +33,11 @@ mod config { /// A sudo-able call. type SudoRuntimeCall: Parameter - + UnfilteredDispatchable + + UnfilteredDispatchable> + GetDispatchInfo; /// Origin checking for council majority - type CouncilOrigin: EnsureOrigin; + type CouncilOrigin: EnsureOrigin<::RuntimeOrigin>; /// Currency type that will be used to place deposits on neurons type Currency: fungible::Balanced diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 01ba58ef3b..a78dcadcee 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -156,7 +156,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] pub fn commit_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, commit_hash: H256, ) -> DispatchResult { @@ -239,7 +239,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(16)) .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] pub fn reveal_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, uids: Vec, values: Vec, @@ -283,7 +283,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] pub fn commit_crv3_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, commit: BoundedVec>, reveal_round: u64, @@ -335,7 +335,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(16)) .saturating_add(T::DbWeight::get().writes(2_u64)), DispatchClass::Normal, Pays::No))] pub fn batch_reveal_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, uids_list: Vec>, values_list: Vec>, @@ -1285,7 +1285,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::Yes))] pub fn set_children( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, children: Vec<(u64, T::AccountId)>, @@ -1641,7 +1641,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)), DispatchClass::Operational, Pays::Yes))] pub fn move_stake( - origin: T::RuntimeOrigin, + origin: OriginFor, origin_hotkey: T::AccountId, destination_hotkey: T::AccountId, origin_netuid: NetUid, @@ -1684,7 +1684,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)), DispatchClass::Operational, Pays::Yes))] pub fn transfer_stake( - origin: T::RuntimeOrigin, + origin: OriginFor, destination_coldkey: T::AccountId, hotkey: T::AccountId, origin_netuid: NetUid, @@ -1729,7 +1729,7 @@ mod dispatches { Pays::Yes ))] pub fn swap_stake( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, origin_netuid: NetUid, destination_netuid: NetUid, @@ -1902,7 +1902,7 @@ mod dispatches { Pays::Yes ))] pub fn swap_stake_limit( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, origin_netuid: NetUid, destination_netuid: NetUid, @@ -1935,10 +1935,7 @@ mod dispatches { DispatchClass::Operational, Pays::Yes ))] - pub fn try_associate_hotkey( - origin: T::RuntimeOrigin, - hotkey: T::AccountId, - ) -> DispatchResult { + pub fn try_associate_hotkey(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { let coldkey = ensure_signed(origin)?; let _ = Self::do_try_associate_hotkey(&coldkey, &hotkey); @@ -1960,7 +1957,7 @@ mod dispatches { DispatchClass::Operational, Pays::Yes ))] - pub fn start_call(origin: T::RuntimeOrigin, netuid: NetUid) -> DispatchResult { + pub fn start_call(origin: OriginFor, netuid: NetUid) -> DispatchResult { Self::do_start_call(origin, netuid)?; Ok(()) } @@ -1999,7 +1996,7 @@ mod dispatches { Pays::Yes ))] pub fn associate_evm_key( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, evm_key: H160, block_number: u64, @@ -2025,7 +2022,7 @@ mod dispatches { Pays::Yes ))] pub fn recycle_alpha( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, amount: AlphaCurrency, netuid: NetUid, @@ -2050,7 +2047,7 @@ mod dispatches { Pays::Yes ))] pub fn burn_alpha( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, amount: AlphaCurrency, netuid: NetUid, @@ -2079,7 +2076,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(30_u64)) .saturating_add(T::DbWeight::get().writes(14_u64)), DispatchClass::Normal, Pays::Yes))] pub fn remove_stake_full_limit( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, limit_price: Option, @@ -2106,7 +2103,7 @@ mod dispatches { #[pallet::call_index(110)] #[pallet::weight(SubnetLeasingWeightInfo::::do_register_leased_network(T::MaxContributors::get()))] pub fn register_leased_network( - origin: T::RuntimeOrigin, + origin: OriginFor, emissions_share: Percent, end_block: Option>, ) -> DispatchResultWithPostInfo { @@ -2132,7 +2129,7 @@ mod dispatches { #[pallet::call_index(111)] #[pallet::weight(SubnetLeasingWeightInfo::::do_terminate_lease(T::MaxContributors::get()))] pub fn terminate_lease( - origin: T::RuntimeOrigin, + origin: OriginFor, lease_id: LeaseId, hotkey: T::AccountId, ) -> DispatchResultWithPostInfo { @@ -2175,5 +2172,366 @@ mod dispatches { Self::deposit_event(Event::SymbolUpdated { netuid, symbol }); Ok(()) } + + /// Add stake. The operation is postponed. + #[pallet::call_index(113)] + #[pallet::weight((Weight::from_parts(99_000_000, 5127) + .saturating_add(T::DbWeight::get().reads(14_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)), DispatchClass::Normal, Pays::Yes))] + pub fn add_stake_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + amount_staked: u64, + ) -> DispatchResult { + Self::do_add_stake_aggregate(origin, hotkey, netuid, amount_staked) + } + + /// Remove stake. The operation is postponed. + #[pallet::call_index(114)] + #[pallet::weight((Weight::from_parts(129_000_000, 10163) + .saturating_add(T::DbWeight::get().reads(19_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)), DispatchClass::Normal, Pays::Yes))] + pub fn remove_stake_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + amount_unstaked: AlphaCurrency, + ) -> DispatchResult { + Self::do_remove_stake_aggregate(origin, hotkey, netuid, amount_unstaked) + } + + /// Add stake with price limit. The operation is postponed. + #[pallet::call_index(115)] + #[pallet::weight((Weight::from_parts(99_000_000, 5127) + .saturating_add(T::DbWeight::get().reads(14_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)), DispatchClass::Normal, Pays::Yes))] + pub fn add_stake_limit_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + amount_staked: u64, + limit_price: u64, + allow_partial: bool, + ) -> DispatchResult { + Self::do_add_stake_limit_aggregate( + origin, + hotkey, + netuid, + amount_staked, + limit_price, + allow_partial, + ) + } + + /// Remove stake with price limit. The operation is postponed. + #[pallet::call_index(116)] + #[pallet::weight((Weight::from_parts(129_000_000, 10163) + .saturating_add(T::DbWeight::get().reads(19_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)), DispatchClass::Normal, Pays::Yes))] + pub fn remove_stake_limit_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + amount_unstaked: AlphaCurrency, + limit_price: u64, + allow_partial: bool, + ) -> DispatchResult { + Self::do_remove_stake_limit_aggregate( + origin, + hotkey, + netuid, + amount_unstaked, + limit_price, + allow_partial, + ) + } + + /// Unstake all subnets. The operation is postponed. + #[pallet::call_index(117)] + #[pallet::weight((Weight::from_parts(3_000_000, 0).saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Operational, Pays::Yes))] + pub fn unstake_all_aggregate(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { + Self::do_unstake_all_aggregate(origin, hotkey) + } + + /// Unstake all alpha from subnets. The operation is postponed. + #[pallet::call_index(118)] + #[pallet::weight((Weight::from_parts(3_000_000, 0).saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Operational, Pays::Yes))] + pub fn unstake_all_alpha_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + ) -> DispatchResult { + Self::do_unstake_all_alpha_aggregate(origin, hotkey) + } + + /// Remove the full stake from the subnet with the price limit. + #[pallet::call_index(119)] + #[pallet::weight((Weight::from_parts(398_000_000, 10142) + .saturating_add(T::DbWeight::get().reads(30_u64)) + .saturating_add(T::DbWeight::get().writes(14_u64)), DispatchClass::Normal, Pays::Yes))] + pub fn remove_stake_full_limit_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + limit_price: Option, + ) -> DispatchResult { + Self::do_remove_stake_full_limit_aggregate(origin, hotkey, netuid, limit_price) + } + + /// Move stake between subnets. + #[pallet::call_index(120)] + #[pallet::weight((Weight::from_parts(157_100_000, 0) + .saturating_add(T::DbWeight::get().reads(15_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)), DispatchClass::Operational, Pays::Yes))] + pub fn move_stake_aggregate( + origin: OriginFor, + origin_hotkey: T::AccountId, + destination_hotkey: T::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + alpha_amount: AlphaCurrency, + ) -> DispatchResult { + Self::do_move_stake_aggregate( + origin, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ) + } + + /// Transfers a specified amount of stake from one coldkey to another, optionally across subnets, + /// while keeping the same hotkey. + #[pallet::call_index(121)] + #[pallet::weight((Weight::from_parts(154_800_000, 0) + .saturating_add(T::DbWeight::get().reads(13_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)), DispatchClass::Operational, Pays::Yes))] + pub fn transfer_stake_aggregate( + origin: OriginFor, + destination_coldkey: T::AccountId, + hotkey: T::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + alpha_amount: AlphaCurrency, + ) -> DispatchResult { + Self::do_transfer_stake_aggregate( + origin, + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ) + } + + /// Swaps a specified amount of stake from one subnet to another, while keeping the same coldkey and hotkey. + #[pallet::call_index(122)] + #[pallet::weight(( + Weight::from_parts(351_300_000, 0) + .saturating_add(T::DbWeight::get().reads(32)) + .saturating_add(T::DbWeight::get().writes(17)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn swap_stake_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + alpha_amount: AlphaCurrency, + ) -> DispatchResult { + Self::do_swap_stake_aggregate( + origin, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ) + } + + /// add_stake with EVM origin + #[pallet::call_index(123)] + #[pallet::weight((Weight::from_parts(345_500_000, 0) + .saturating_add(T::DbWeight::get().reads(26)) + .saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::Yes))] + pub fn add_stake_evm( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + amount_staked: u64, + ) -> DispatchResult { + let account_id = crate::ensure_evm_origin(::RuntimeOrigin::from(origin))?; + let verified_evm_origin = RawOrigin::Signed(account_id); + + Self::do_add_stake(verified_evm_origin.into(), hotkey, netuid, amount_staked) + } + + /// remove_stake with EVM origin + #[pallet::call_index(124)] + #[pallet::weight((Weight::from_parts(196_800_000, 0) + .saturating_add(T::DbWeight::get().reads(19)) + .saturating_add(T::DbWeight::get().writes(10)), DispatchClass::Normal, Pays::Yes))] + pub fn remove_stake_evm( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + amount_unstaked: AlphaCurrency, + ) -> DispatchResult { + let account_id = crate::ensure_evm_origin(::RuntimeOrigin::from(origin))?; + let verified_evm_origin = RawOrigin::Signed(account_id); + + Self::do_remove_stake(verified_evm_origin.into(), hotkey, netuid, amount_unstaked) + } + + /// add_stake_limit with EVM origin + #[pallet::call_index(125)] + #[pallet::weight((Weight::from_parts(402_800_000, 0) + .saturating_add(T::DbWeight::get().reads(26)) + .saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::Yes))] + pub fn add_stake_limit_evm( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + amount_staked: u64, + limit_price: u64, + allow_partial: bool, + ) -> DispatchResult { + let account_id = crate::ensure_evm_origin(::RuntimeOrigin::from(origin))?; + let verified_evm_origin = RawOrigin::Signed(account_id); + + Self::do_add_stake_limit( + verified_evm_origin.into(), + hotkey, + netuid, + amount_staked, + limit_price, + allow_partial, + ) + } + + /// remove_stake_limit with EVM origin + #[pallet::call_index(126)] + #[pallet::weight((Weight::from_parts(403_800_000, 0) + .saturating_add(T::DbWeight::get().reads(30)) + .saturating_add(T::DbWeight::get().writes(14)), DispatchClass::Normal, Pays::Yes))] + pub fn remove_stake_limit_evm( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + amount_unstaked: AlphaCurrency, + limit_price: u64, + allow_partial: bool, + ) -> DispatchResult { + let account_id = crate::ensure_evm_origin(::RuntimeOrigin::from(origin))?; + let verified_evm_origin = RawOrigin::Signed(account_id); + + Self::do_remove_stake_limit( + verified_evm_origin.into(), + hotkey, + netuid, + amount_unstaked, + limit_price, + allow_partial, + ) + } + + /// move_stake with EVM origin + #[pallet::call_index(127)] + #[pallet::weight((Weight::from_parts(157_100_000, 0) + .saturating_add(T::DbWeight::get().reads(15_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)), DispatchClass::Operational, Pays::Yes))] + pub fn move_stake_evm( + origin: OriginFor, + origin_hotkey: T::AccountId, + destination_hotkey: T::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + alpha_amount: AlphaCurrency, + ) -> DispatchResult { + let account_id = crate::ensure_evm_origin(::RuntimeOrigin::from(origin))?; + let verified_evm_origin = RawOrigin::Signed(account_id); + + Self::do_move_stake( + verified_evm_origin.into(), + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ) + } + + /// transfer_stake with EVM origin + #[pallet::call_index(128)] + #[pallet::weight((Weight::from_parts(154_800_000, 0) + .saturating_add(T::DbWeight::get().reads(13_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)), DispatchClass::Operational, Pays::Yes))] + pub fn transfer_stake_evm( + origin: OriginFor, + destination_coldkey: T::AccountId, + hotkey: T::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + alpha_amount: AlphaCurrency, + ) -> DispatchResult { + let account_id = crate::ensure_evm_origin(::RuntimeOrigin::from(origin))?; + let verified_evm_origin = RawOrigin::Signed(account_id); + + Self::do_transfer_stake( + verified_evm_origin.into(), + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ) + } + + /// remove_stake_full_limit with EVM origin + #[pallet::call_index(129)] + #[pallet::weight((Weight::from_parts(398_000_000, 10142) + .saturating_add(T::DbWeight::get().reads(30_u64)) + .saturating_add(T::DbWeight::get().writes(14_u64)), DispatchClass::Normal, Pays::Yes))] + pub fn remove_stake_full_limit_evm( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + limit_price: Option, + ) -> DispatchResult { + let account_id = crate::ensure_evm_origin(::RuntimeOrigin::from(origin))?; + let verified_evm_origin = RawOrigin::Signed(account_id); + + Self::do_remove_stake_full_limit(verified_evm_origin.into(), hotkey, netuid, limit_price) + } + + /// Aggregated version of swap_stake_limit + #[pallet::call_index(130)] + #[pallet::weight(( + Weight::from_parts(426_500_000, 0) + .saturating_add(T::DbWeight::get().reads(32)) + .saturating_add(T::DbWeight::get().writes(17)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn swap_stake_limit_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + alpha_amount: AlphaCurrency, + limit_price: u64, + allow_partial: bool, + ) -> DispatchResult { + Self::do_swap_stake_limit_aggregate( + origin, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + ) + } } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index c0528d0600..62d704dddd 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -380,5 +380,258 @@ mod events { /// The symbol that has been updated. symbol: Vec, }, + + /// stake has been transferred from the coldkey account onto the hotkey staking account (at the end of the block) + AggregatedStakeAdded { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + /// The subnet ID + netuid: NetUid, + /// Stake + stake_to_be_added: u64, + }, + /// adding aggregated stake has failed + FailedToAddAggregatedStake { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + /// The subnet ID + netuid: NetUid, + /// Stake + stake_to_be_added: u64, + }, + /// limited stake has been transferred from the coldkey account onto the hotkey staking account (at the end of the block) + AggregatedLimitedStakeAdded { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + /// The subnet ID + netuid: NetUid, + /// stake + stake_to_be_added: u64, + /// price limit + limit_price: u64, + /// allow partial stake removal + allow_partial: bool, + }, + /// adding limited aggregated stake has failed + FailedToAddAggregatedLimitedStake { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + /// The subnet ID + netuid: NetUid, + /// stake + stake_to_be_added: u64, + /// price limit + limit_price: u64, + /// allow partial stake removal + allow_partial: bool, + }, + /// stake has been removed from the hotkey staking account into the coldkey account (at the end of the block). + AggregatedStakeRemoved { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + /// The subnet ID + netuid: NetUid, + /// alpha + alpha_unstaked: AlphaCurrency, + }, + /// removing aggregated stake has failed + FailedToRemoveAggregatedStake { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + /// The subnet ID + netuid: NetUid, + /// alpha + alpha_unstaked: AlphaCurrency, + }, + /// aggregated limited stake has been removed from the hotkey staking account into the coldkey account (at the end of the block). + AggregatedLimitedStakeRemoved { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + /// The subnet ID + netuid: NetUid, + /// alpha + alpha_unstaked: AlphaCurrency, + /// price limit + limit_price: u64, + /// allow partial stake removal + allow_partial: bool, + }, + /// removing limited aggregated stake has failed + FailedToRemoveAggregatedLimitedStake { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + /// The subnet ID + netuid: NetUid, + /// alpha + alpha_unstaked: AlphaCurrency, + /// price limit + limit_price: u64, + /// allow partial stake removal + allow_partial: bool, + }, + /// aggregated unstake_all operation has succeeded + AggregatedUnstakeAllSucceeded { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + }, + /// aggregated unstake_all operation has failed + AggregatedUnstakeAllFailed { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + }, + /// aggregated unstake_all_alpha operation has succeeded + AggregatedUnstakeAllAlphaSucceeded { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + }, + /// aggregated unstake_all_alpha operation has failed + AggregatedUnstakeAllAlphaFailed { + /// the account ID of coldkey + coldkey: T::AccountId, + /// the account ID of hotkey + hotkey: T::AccountId, + }, + /// Aggregated version of `move_stake` executed successfully + AggregatedStakeMoved { + /// Coldkey account + coldkey: T::AccountId, + /// Origin hotkey account + origin_hotkey: T::AccountId, + /// Destination hotkey account + destination_hotkey: T::AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + }, + /// Aggregated version of `move_stake` has failed + FailedToMoveAggregatedStake { + /// Coldkey account + coldkey: T::AccountId, + /// Origin hotkey account + origin_hotkey: T::AccountId, + /// Destination hotkey account + destination_hotkey: T::AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + }, + /// Aggregated version of `transfer_stake` executed successfully + AggregatedStakeTransferred { + /// Origin coldkey account + origin_coldkey: T::AccountId, + /// Destination coldkey account + destination_coldkey: T::AccountId, + /// Hotkey account + hotkey: T::AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + }, + /// Aggregated version of `transfer_stake` has failed + FailedToTransferAggregatedStake { + /// Origin coldkey account + origin_coldkey: T::AccountId, + /// Destination coldkey account + destination_coldkey: T::AccountId, + /// Hotkey account + hotkey: T::AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + }, + /// Aggregated version of `swap_stake` executed successfully + AggregatedStakeSwapped { + /// Coldkey account + coldkey: T::AccountId, + /// Hotkey account + hotkey: T::AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + }, + /// Aggregated version of `swap_stake` has failed + FailedToSwapAggregatedStake { + /// Coldkey account + coldkey: T::AccountId, + /// Hotkey account + hotkey: T::AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + }, + /// Aggregated version of `swap_stake_limit` executed successfully + AggregatedLimitedStakeSwapped { + /// Coldkey account + coldkey: T::AccountId, + /// Hotkey account + hotkey: T::AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + /// price limit + limit_price: u64, + /// allow partial stake removal + allow_partial: bool, + }, + /// Aggregated version of `swap_stake_limit` executed successfully + FailedToSwapLimitedAggregatedStake { + /// Coldkey account + coldkey: T::AccountId, + /// Hotkey account + hotkey: T::AccountId, + /// Origin subnet UID + origin_netuid: NetUid, + /// Destination subnet UID + destination_netuid: NetUid, + /// Alpha + alpha_amount: AlphaCurrency, + /// price limit + limit_price: u64, + /// allow partial stake removal + allow_partial: bool, + }, } } diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 7083c24e5d..3f1e734347 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -43,10 +43,12 @@ mod hooks { // # Args: // * 'n': (BlockNumberFor): // - The number of the block we are finalizing. - fn on_finalize(_block_number: BlockNumberFor) { + fn on_finalize(block_number: BlockNumberFor) { for _ in StakingOperationRateLimiter::::drain() { // Clear all entries each block } + + Self::process_staking_jobs(block_number); } fn on_runtime_upgrade() -> frame_support::weights::Weight { diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index 7388c9484a..8f3088956e 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -38,7 +38,7 @@ impl Pallet { /// - Thrown if key has hit transaction rate limit /// pub fn do_add_stake( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, stake_to_be_added: u64, @@ -125,7 +125,7 @@ impl Pallet { /// - Thrown if key has hit transaction rate limit /// pub fn do_add_stake_limit( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, stake_to_be_added: u64, @@ -206,4 +206,79 @@ impl Pallet { Err(Error::ZeroMaxStakeAmount) } } + + pub fn do_add_stake_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + stake_to_be_added: u64, + ) -> dispatch::DispatchResult { + // We check that the transaction is signed by the caller and retrieve the T::AccountId coldkey information. + let coldkey = ensure_signed(origin)?; + + // Consider the weight from on_finalize + if cfg!(feature = "runtime-benchmarks") && !cfg!(test) { + Self::do_add_stake( + crate::dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + stake_to_be_added, + )?; + } + + // Save the staking job for the on_finalize + let stake_job = StakeJob::AddStake { + hotkey, + coldkey, + netuid, + stake_to_be_added, + }; + + let stake_job_id = NextStakeJobId::::get(); + let current_blocknumber = >::block_number(); + + StakeJobs::::insert(current_blocknumber, stake_job_id, stake_job); + NextStakeJobId::::set(stake_job_id.saturating_add(1)); + + Ok(()) + } + + pub fn do_add_stake_limit_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + stake_to_be_added: u64, + limit_price: u64, + allow_partial: bool, + ) -> dispatch::DispatchResult { + let coldkey = ensure_signed(origin)?; + + if cfg!(feature = "runtime-benchmarks") && !cfg!(test) { + Self::do_add_stake_limit( + crate::dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + stake_to_be_added, + limit_price, + allow_partial, + )?; + } + + let stake_job = StakeJob::AddStakeLimit { + hotkey, + coldkey, + netuid, + stake_to_be_added, + limit_price, + allow_partial, + }; + + let stake_job_id = NextStakeJobId::::get(); + let current_blocknumber = >::block_number(); + + StakeJobs::::insert(current_blocknumber, stake_job_id, stake_job); + NextStakeJobId::::set(stake_job_id.saturating_add(1)); + + Ok(()) + } } diff --git a/pallets/subtensor/src/staking/decrease_take.rs b/pallets/subtensor/src/staking/decrease_take.rs index c227aad8a3..745e7633c6 100644 --- a/pallets/subtensor/src/staking/decrease_take.rs +++ b/pallets/subtensor/src/staking/decrease_take.rs @@ -28,7 +28,7 @@ impl Pallet { /// - The delegate is setting a take which is not lower than the previous. /// pub fn do_decrease_take( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, take: u16, ) -> dispatch::DispatchResult { diff --git a/pallets/subtensor/src/staking/increase_take.rs b/pallets/subtensor/src/staking/increase_take.rs index 349f86e7cf..e05a2db788 100644 --- a/pallets/subtensor/src/staking/increase_take.rs +++ b/pallets/subtensor/src/staking/increase_take.rs @@ -31,7 +31,7 @@ impl Pallet { /// - The delegate is setting a take which is not greater than the previous. /// pub fn do_increase_take( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, take: u16, ) -> dispatch::DispatchResult { diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 15f2dd511a..81eb469808 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -28,7 +28,7 @@ impl Pallet { /// # Events /// Emits a `StakeMoved` event upon successful completion of the stake movement. pub fn do_move_stake( - origin: T::RuntimeOrigin, + origin: OriginFor, origin_hotkey: T::AccountId, destination_hotkey: T::AccountId, origin_netuid: NetUid, @@ -123,7 +123,7 @@ impl Pallet { /// # Events /// Emits a `StakeTransferred` event upon successful completion of the transfer. pub fn do_transfer_stake( - origin: T::RuntimeOrigin, + origin: OriginFor, destination_coldkey: T::AccountId, hotkey: T::AccountId, origin_netuid: NetUid, @@ -195,7 +195,7 @@ impl Pallet { /// # Events /// Emits a `StakeSwapped` event upon successful completion. pub fn do_swap_stake( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, origin_netuid: NetUid, destination_netuid: NetUid, @@ -266,7 +266,7 @@ impl Pallet { /// # Events /// Emits a `StakeSwapped` event upon successful completion. pub fn do_swap_stake_limit( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, origin_netuid: NetUid, destination_netuid: NetUid, @@ -534,4 +534,167 @@ impl Pallet { Err(Error::ZeroMaxStakeAmount) } } + + pub fn do_move_stake_aggregate( + origin: OriginFor, + origin_hotkey: T::AccountId, + destination_hotkey: T::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + alpha_amount: AlphaCurrency, + ) -> dispatch::DispatchResult { + let coldkey = ensure_signed(origin)?; + + // Consider the weight from on_finalize + if cfg!(feature = "runtime-benchmarks") && !cfg!(test) { + Self::do_move_stake( + crate::dispatch::RawOrigin::Signed(coldkey.clone()).into(), + origin_hotkey.clone(), + destination_hotkey.clone(), + origin_netuid, + destination_netuid, + alpha_amount, + )?; + } + + // Save the staking job for the on_finalize + let stake_job = StakeJob::MoveStake { + coldkey, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }; + + let stake_job_id = NextStakeJobId::::get(); + let current_blocknumber = >::block_number(); + + StakeJobs::::insert(current_blocknumber, stake_job_id, stake_job); + NextStakeJobId::::set(stake_job_id.saturating_add(1)); + + Ok(()) + } + + pub fn do_transfer_stake_aggregate( + origin: OriginFor, + destination_coldkey: T::AccountId, + hotkey: T::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + alpha_amount: AlphaCurrency, + ) -> dispatch::DispatchResult { + let coldkey = ensure_signed(origin)?; + + // Consider the weight from on_finalize + if cfg!(feature = "runtime-benchmarks") && !cfg!(test) { + Self::do_transfer_stake( + crate::dispatch::RawOrigin::Signed(coldkey.clone()).into(), + destination_coldkey.clone(), + hotkey.clone(), + origin_netuid, + destination_netuid, + alpha_amount, + )?; + } + + // Save the staking job for the on_finalize + let stake_job = StakeJob::TransferStake { + origin_coldkey: coldkey, + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }; + + let stake_job_id = NextStakeJobId::::get(); + let current_blocknumber = >::block_number(); + + StakeJobs::::insert(current_blocknumber, stake_job_id, stake_job); + NextStakeJobId::::set(stake_job_id.saturating_add(1)); + + Ok(()) + } + + pub fn do_swap_stake_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + alpha_amount: AlphaCurrency, + ) -> dispatch::DispatchResult { + let coldkey = ensure_signed(origin)?; + + // Consider the weight from on_finalize + if cfg!(feature = "runtime-benchmarks") && !cfg!(test) { + Self::do_swap_stake( + crate::dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + origin_netuid, + destination_netuid, + alpha_amount, + )?; + } + + // Save the staking job for the on_finalize + let stake_job = StakeJob::SwapStake { + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }; + + let stake_job_id = NextStakeJobId::::get(); + let current_blocknumber = >::block_number(); + + StakeJobs::::insert(current_blocknumber, stake_job_id, stake_job); + NextStakeJobId::::set(stake_job_id.saturating_add(1)); + + Ok(()) + } + pub fn do_swap_stake_limit_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + origin_netuid: NetUid, + destination_netuid: NetUid, + alpha_amount: AlphaCurrency, + limit_price: u64, + allow_partial: bool, + ) -> dispatch::DispatchResult { + let coldkey = ensure_signed(origin)?; + + // Consider the weight from on_finalize + if cfg!(feature = "runtime-benchmarks") && !cfg!(test) { + Self::do_swap_stake_limit( + crate::dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + )?; + } + + // Save the staking job for the on_finalize + let stake_job = StakeJob::SwapStakeLimit { + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + }; + + let stake_job_id = NextStakeJobId::::get(); + let current_blocknumber = >::block_number(); + + StakeJobs::::insert(current_blocknumber, stake_job_id, stake_job); + NextStakeJobId::::set(stake_job_id.saturating_add(1)); + + Ok(()) + } } diff --git a/pallets/subtensor/src/staking/recycle_alpha.rs b/pallets/subtensor/src/staking/recycle_alpha.rs index 2fbbfb3b06..23cfcbb3ad 100644 --- a/pallets/subtensor/src/staking/recycle_alpha.rs +++ b/pallets/subtensor/src/staking/recycle_alpha.rs @@ -16,7 +16,7 @@ impl Pallet { /// /// * `DispatchResult` - Success or error pub(crate) fn do_recycle_alpha( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, amount: AlphaCurrency, netuid: NetUid, @@ -82,7 +82,7 @@ impl Pallet { /// /// * `DispatchResult` - Success or error pub(crate) fn do_burn_alpha( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, amount: AlphaCurrency, netuid: NetUid, diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index afe9d19308..d26a430805 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -37,7 +37,7 @@ impl Pallet { /// - Thrown if key has hit transaction rate limit /// pub fn do_remove_stake( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, alpha_unstaked: AlphaCurrency, @@ -117,10 +117,7 @@ impl Pallet { /// * 'TxRateLimitExceeded': /// - Thrown if key has hit transaction rate limit /// - pub fn do_unstake_all( - origin: T::RuntimeOrigin, - hotkey: T::AccountId, - ) -> dispatch::DispatchResult { + pub fn do_unstake_all(origin: OriginFor, hotkey: T::AccountId) -> dispatch::DispatchResult { // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; log::debug!("do_unstake_all( origin:{:?} hotkey:{:?} )", coldkey, hotkey); @@ -208,7 +205,7 @@ impl Pallet { /// - Thrown if key has hit transaction rate limit /// pub fn do_unstake_all_alpha( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, ) -> dispatch::DispatchResult { // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. @@ -327,7 +324,7 @@ impl Pallet { /// - Thrown if there is not enough stake on the hotkey to withdwraw this amount. /// pub fn do_remove_stake_limit( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, alpha_unstaked: AlphaCurrency, @@ -424,7 +421,7 @@ impl Pallet { } pub fn do_remove_stake_full_limit( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, limit_price: Option, @@ -440,4 +437,160 @@ impl Pallet { Self::do_remove_stake(origin, hotkey, netuid, alpha_unstaked) } } + + pub fn do_remove_stake_full_limit_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + limit_price: Option, + ) -> DispatchResult { + let coldkey = ensure_signed(origin.clone())?; + + let alpha_unstaked = + Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + + if let Some(limit_price) = limit_price { + Self::do_remove_stake_limit_aggregate( + origin, + hotkey, + netuid, + alpha_unstaked, + limit_price, + false, + ) + } else { + Self::do_remove_stake_aggregate(origin, hotkey, netuid, alpha_unstaked) + } + } + + pub fn do_remove_stake_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + alpha_unstaked: AlphaCurrency, + ) -> dispatch::DispatchResult { + // We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. + let coldkey = ensure_signed(origin)?; + + // Consider the weight from on_finalize + if cfg!(feature = "runtime-benchmarks") && !cfg!(test) { + Self::do_remove_stake( + crate::dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + alpha_unstaked, + )?; + } + + // Save the staking job for the on_finalize + let stake_job = StakeJob::RemoveStake { + hotkey, + coldkey, + netuid, + alpha_unstaked, + }; + + let stake_job_id = NextStakeJobId::::get(); + let current_blocknumber = >::block_number(); + + StakeJobs::::insert(current_blocknumber, stake_job_id, stake_job); + NextStakeJobId::::set(stake_job_id.saturating_add(1)); + + Ok(()) + } + + pub fn do_remove_stake_limit_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + alpha_unstaked: AlphaCurrency, + limit_price: u64, + allow_partial: bool, + ) -> dispatch::DispatchResult { + let coldkey = ensure_signed(origin)?; + + // Consider the weight from on_finalize + if cfg!(feature = "runtime-benchmarks") && !cfg!(test) { + Self::do_remove_stake_limit( + crate::dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + alpha_unstaked, + limit_price, + allow_partial, + )?; + } + + let stake_job = StakeJob::RemoveStakeLimit { + hotkey, + coldkey, + netuid, + alpha_unstaked, + limit_price, + allow_partial, + }; + + let stake_job_id = NextStakeJobId::::get(); + let current_blocknumber = >::block_number(); + + StakeJobs::::insert(current_blocknumber, stake_job_id, stake_job); + NextStakeJobId::::set(stake_job_id.saturating_add(1)); + + // Done and ok. + Ok(()) + } + + pub fn do_unstake_all_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + ) -> dispatch::DispatchResult { + // We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. + let coldkey = ensure_signed(origin)?; + + // Consider the weight from on_finalize + if cfg!(feature = "runtime-benchmarks") && !cfg!(test) { + Self::do_unstake_all( + crate::dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + )?; + } + + // Save the unstake_all job for the on_finalize + let stake_job = StakeJob::UnstakeAll { hotkey, coldkey }; + + let stake_job_id = NextStakeJobId::::get(); + let current_blocknumber = >::block_number(); + + StakeJobs::::insert(current_blocknumber, stake_job_id, stake_job); + NextStakeJobId::::set(stake_job_id.saturating_add(1)); + + Ok(()) + } + + pub fn do_unstake_all_alpha_aggregate( + origin: OriginFor, + hotkey: T::AccountId, + ) -> dispatch::DispatchResult { + // We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. + let coldkey = ensure_signed(origin)?; + + // Consider the weight from on_finalize + if cfg!(feature = "runtime-benchmarks") && !cfg!(test) { + Self::do_unstake_all_alpha( + crate::dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + )?; + } + + // Save the unstake_all_alpha job for the on_finalize + let stake_job = StakeJob::UnstakeAllAlpha { hotkey, coldkey }; + + let stake_job_id = NextStakeJobId::::get(); + let current_blocknumber = >::block_number(); + + StakeJobs::::insert(current_blocknumber, stake_job_id, stake_job); + NextStakeJobId::::set(stake_job_id.saturating_add(1)); + + Ok(()) + } } diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 35867f82ca..0fc19370ee 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -35,7 +35,7 @@ impl Pallet { /// - Too many children in request /// pub fn do_schedule_children( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, children: Vec<(u64, T::AccountId)>, diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index a244f9f5a7..a4d96721cb 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -1,6 +1,11 @@ use super::*; +use frame_support::storage::transactional; +use frame_support::traits::Randomness; +use frame_system::pallet_prelude::BlockNumberFor; use safe_math::*; use share_pool::{SharePool, SharePoolDataOperations}; +use sp_core::blake2_128; +use sp_runtime::TransactionOutcome; use sp_std::ops::Neg; use substrate_fixed::types::{I64F64, I96F32, U64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid}; @@ -1305,6 +1310,541 @@ impl Pallet { Ok(()) } + + // Process staking job for on_finalize() hook. + pub fn process_staking_jobs(_current_block_number: BlockNumberFor) { + let stake_jobs = StakeJobs::::drain().collect::>(); + + // Sort jobs by job type + let mut add_stake = vec![]; + let mut remove_stake = vec![]; + let mut add_stake_limit = vec![]; + let mut remove_stake_limit = vec![]; + let mut unstake_all = vec![]; + let mut unstake_all_aplha = vec![]; + let mut move_stake = vec![]; + let mut transfer_stake = vec![]; + let mut swap_stake = vec![]; + let mut swap_stake_limit = vec![]; + + for (_, _, job) in stake_jobs.into_iter() { + match &job { + StakeJob::AddStake { .. } => add_stake.push(job), + StakeJob::RemoveStake { .. } => remove_stake.push(job), + StakeJob::AddStakeLimit { .. } => add_stake_limit.push(job), + StakeJob::RemoveStakeLimit { .. } => remove_stake_limit.push(job), + StakeJob::UnstakeAll { .. } => unstake_all.push(job), + StakeJob::UnstakeAllAlpha { .. } => unstake_all_aplha.push(job), + StakeJob::MoveStake { .. } => move_stake.push(job), + StakeJob::TransferStake { .. } => transfer_stake.push(job), + StakeJob::SwapStake { .. } => swap_stake.push(job), + StakeJob::SwapStakeLimit { .. } => swap_stake_limit.push(job), + } + } + // Reorder jobs based on the last drand pulse + let (randomness, _) = as Randomness< + T::Hash, + BlockNumberFor, + >>::random(b"staking_ops"); + let random_hash = blake2_128(randomness.as_ref()); + let first_byte = random_hash.as_ref().first().unwrap_or(&0); + // Extract the first bit + let altered_order = (*first_byte & 0b10000000) != 0; + + // Ascending sort by coldkey + let compare_coldkeys = |a_key: &T::AccountId, b_key: &T::AccountId| { + let direct_order = a_key.cmp(b_key); + if altered_order { + direct_order.reverse() + } else { + direct_order + } + }; + + remove_stake_limit.sort_by(|a, b| compare_coldkeys(&a.coldkey(), &b.coldkey())); + remove_stake.sort_by(|a, b| compare_coldkeys(&a.coldkey(), &b.coldkey())); + unstake_all.sort_by(|a, b| compare_coldkeys(&a.coldkey(), &b.coldkey())); + unstake_all_aplha.sort_by(|a, b| compare_coldkeys(&a.coldkey(), &b.coldkey())); + add_stake_limit.sort_by(|a, b| compare_coldkeys(&a.coldkey(), &b.coldkey())); + add_stake.sort_by(|a, b| compare_coldkeys(&a.coldkey(), &b.coldkey())); + move_stake.sort_by(|a, b| compare_coldkeys(&a.coldkey(), &b.coldkey())); + transfer_stake.sort_by(|a, b| compare_coldkeys(&a.coldkey(), &b.coldkey())); + swap_stake.sort_by(|a, b| compare_coldkeys(&a.coldkey(), &b.coldkey())); + swap_stake_limit.sort_by(|a, b| compare_coldkeys(&a.coldkey(), &b.coldkey())); + + let job_batches = vec![ + add_stake, + add_stake_limit, + remove_stake, + remove_stake_limit, + unstake_all, + unstake_all_aplha, + move_stake, + transfer_stake, + swap_stake, + swap_stake_limit, + ]; + + for jobs in job_batches.into_iter() { + for job in jobs.into_iter() { + let result = transactional::with_transaction(|| { + let result = Self::run_single_staking_job(job.clone()); + match result { + Ok(()) => TransactionOutcome::Commit(Ok(())), + Err(err) => TransactionOutcome::Rollback(Err(err)), + } + }); + + Self::handle_single_staking_job_result(job, result); + } + } + } + + fn run_single_staking_job(job: StakeJob) -> DispatchResult { + match job { + StakeJob::RemoveStakeLimit { + hotkey, + coldkey, + netuid, + alpha_unstaked, + limit_price, + allow_partial, + } => Self::do_remove_stake_limit( + dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + alpha_unstaked, + limit_price, + allow_partial, + ), + StakeJob::RemoveStake { + coldkey, + hotkey, + netuid, + alpha_unstaked, + } => Self::do_remove_stake( + dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + alpha_unstaked, + ), + StakeJob::UnstakeAll { hotkey, coldkey } => Self::do_unstake_all( + dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + ), + StakeJob::UnstakeAllAlpha { hotkey, coldkey } => Self::do_unstake_all_alpha( + dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + ), + StakeJob::AddStakeLimit { + hotkey, + coldkey, + netuid, + stake_to_be_added, + limit_price, + allow_partial, + } => Self::do_add_stake_limit( + dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + stake_to_be_added, + limit_price, + allow_partial, + ), + StakeJob::AddStake { + hotkey, + coldkey, + netuid, + stake_to_be_added, + } => Self::do_add_stake( + dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + netuid, + stake_to_be_added, + ), + StakeJob::MoveStake { + coldkey, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + } => Self::do_move_stake( + dispatch::RawOrigin::Signed(coldkey.clone()).into(), + origin_hotkey.clone(), + destination_hotkey.clone(), + origin_netuid, + destination_netuid, + alpha_amount, + ), + StakeJob::TransferStake { + origin_coldkey, + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + } => Self::do_transfer_stake( + dispatch::RawOrigin::Signed(origin_coldkey.clone()).into(), + destination_coldkey.clone(), + hotkey.clone(), + origin_netuid, + destination_netuid, + alpha_amount, + ), + StakeJob::SwapStake { + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + } => Self::do_swap_stake( + dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + origin_netuid, + destination_netuid, + alpha_amount, + ), + StakeJob::SwapStakeLimit { + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + } => Self::do_swap_stake_limit( + dispatch::RawOrigin::Signed(coldkey.clone()).into(), + hotkey.clone(), + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + ), + } + } // returns event of the failed job + fn handle_single_staking_job_result(job: StakeJob, result: DispatchResult) { + match job { + StakeJob::RemoveStakeLimit { + hotkey, + coldkey, + netuid, + alpha_unstaked, + limit_price, + allow_partial, + } => { + if let Err(err) = &result { + log::debug!( + "Failed to remove aggregated limited stake: {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}", + coldkey, + hotkey, + netuid, + alpha_unstaked, + limit_price, + allow_partial, + err + ); + Self::deposit_event(Event::FailedToRemoveAggregatedLimitedStake { + coldkey, + hotkey, + netuid, + alpha_unstaked, + limit_price, + allow_partial, + }); + } else { + Self::deposit_event(Event::AggregatedLimitedStakeRemoved { + coldkey, + hotkey, + netuid, + alpha_unstaked, + limit_price, + allow_partial, + }); + } + } + StakeJob::RemoveStake { + coldkey, + hotkey, + netuid, + alpha_unstaked, + } => { + if let Err(err) = &result { + log::debug!( + "Failed to remove aggregated stake: {:?}, {:?}, {:?}, {:?}, {:?}", + coldkey, + hotkey, + netuid, + alpha_unstaked, + err + ); + Self::deposit_event(Event::FailedToRemoveAggregatedStake { + coldkey, + hotkey, + netuid, + alpha_unstaked, + }); + } else { + Self::deposit_event(Event::AggregatedStakeRemoved { + coldkey, + hotkey, + netuid, + alpha_unstaked, + }); + } + } + StakeJob::UnstakeAll { hotkey, coldkey } => { + if let Err(err) = &result { + log::debug!( + "Failed to unstake all: {:?}, {:?}, {:?}", + coldkey, + hotkey, + err + ); + Self::deposit_event(Event::AggregatedUnstakeAllFailed { coldkey, hotkey }); + } else { + Self::deposit_event(Event::AggregatedUnstakeAllSucceeded { coldkey, hotkey }); + } + } + StakeJob::UnstakeAllAlpha { hotkey, coldkey } => { + if let Err(err) = &result { + log::debug!( + "Failed to unstake all alpha: {:?}, {:?}, {:?}", + coldkey, + hotkey, + err + ); + Self::deposit_event(Event::AggregatedUnstakeAllAlphaFailed { coldkey, hotkey }); + } else { + Self::deposit_event(Event::AggregatedUnstakeAllAlphaSucceeded { + coldkey, + hotkey, + }); + } + } + StakeJob::AddStakeLimit { + hotkey, + coldkey, + netuid, + stake_to_be_added, + limit_price, + allow_partial, + } => { + if let Err(err) = &result { + log::debug!( + "Failed to add aggregated limited stake: {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}", + coldkey, + hotkey, + netuid, + stake_to_be_added, + limit_price, + allow_partial, + err + ); + Self::deposit_event(Event::FailedToAddAggregatedLimitedStake { + coldkey, + hotkey, + netuid, + stake_to_be_added, + limit_price, + allow_partial, + }); + } else { + Self::deposit_event(Event::AggregatedLimitedStakeAdded { + coldkey, + hotkey, + netuid, + stake_to_be_added, + limit_price, + allow_partial, + }); + } + } + StakeJob::AddStake { + hotkey, + coldkey, + netuid, + stake_to_be_added, + } => { + if let Err(err) = result { + log::debug!( + "Failed to add aggregated stake: {:?}, {:?}, {:?}, {:?}, {:?}", + coldkey, + hotkey, + netuid, + stake_to_be_added, + err + ); + Self::deposit_event(Event::FailedToAddAggregatedStake { + coldkey, + hotkey, + netuid, + stake_to_be_added, + }); + } else { + Self::deposit_event(Event::AggregatedStakeAdded { + coldkey, + hotkey, + netuid, + stake_to_be_added, + }); + } + } + StakeJob::MoveStake { + coldkey, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + } => { + if let Err(err) = &result { + log::debug!( + "Failed to move aggregated stake: {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}", + coldkey, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + err + ); + Self::deposit_event(Event::FailedToMoveAggregatedStake { + coldkey, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }); + } else { + Self::deposit_event(Event::AggregatedStakeMoved { + coldkey, + origin_hotkey, + destination_hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }); + } + } + StakeJob::TransferStake { + origin_coldkey, + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + } => { + if let Err(err) = &result { + log::debug!( + "Failed to transfer aggregated stake: {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}", + origin_coldkey, + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + err + ); + Self::deposit_event(Event::FailedToTransferAggregatedStake { + origin_coldkey, + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }); + } else { + Self::deposit_event(Event::AggregatedStakeTransferred { + origin_coldkey, + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }); + } + } + StakeJob::SwapStake { + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + } => { + if let Err(err) = &result { + log::debug!( + "Failed to swap aggregated stake: {:?}, {:?}, {:?}, {:?}, {:?}, {:?}", + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + err + ); + Self::deposit_event(Event::FailedToSwapAggregatedStake { + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }); + } else { + Self::deposit_event(Event::AggregatedStakeSwapped { + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + }); + } + } + StakeJob::SwapStakeLimit { + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + } => { + if let Err(err) = &result { + log::debug!( + "Failed to swap limited aggregated stake: {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}", + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + err + ); + Self::deposit_event(Event::FailedToSwapLimitedAggregatedStake { + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + }); + } else { + Self::deposit_event(Event::AggregatedLimitedStakeSwapped { + coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + limit_price, + allow_partial, + }); + } + } + } + } } /////////////////////////////////////////// diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index de2b5fa689..6067f666c7 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -67,7 +67,7 @@ impl Pallet { /// /// The leftover cap is refunded to the contributors and the beneficiary. pub fn do_register_leased_network( - origin: T::RuntimeOrigin, + origin: OriginFor, emissions_share: Percent, end_block: Option>, ) -> DispatchResultWithPostInfo { @@ -191,7 +191,7 @@ impl Pallet { /// The beneficiary can terminate the lease after the end block has passed and get the subnet ownership. /// The subnet is transferred to the beneficiary and the lease is removed from storage. pub fn do_terminate_lease( - origin: T::RuntimeOrigin, + origin: OriginFor, lease_id: LeaseId, hotkey: T::AccountId, ) -> DispatchResultWithPostInfo { diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index 67631d3fc1..9027146b31 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -65,7 +65,7 @@ impl Pallet { /// - The hotkey is already registered on this network. /// pub fn do_burned_registration( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, hotkey: T::AccountId, ) -> DispatchResult { @@ -222,7 +222,7 @@ impl Pallet { /// - The seal is incorrect. /// pub fn do_registration( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, block_number: u64, nonce: u64, @@ -349,7 +349,7 @@ impl Pallet { } pub fn do_faucet( - origin: T::RuntimeOrigin, + origin: OriginFor, block_number: u64, nonce: u64, work: Vec, diff --git a/pallets/subtensor/src/subnets/serving.rs b/pallets/subtensor/src/subnets/serving.rs index ae1c97cc7c..b9a73233c0 100644 --- a/pallets/subtensor/src/subnets/serving.rs +++ b/pallets/subtensor/src/subnets/serving.rs @@ -56,7 +56,7 @@ impl Pallet { /// - Attempting to set prometheus information withing the rate limit min. /// pub fn do_serve_axon( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, version: u32, ip: u128, @@ -160,7 +160,7 @@ impl Pallet { /// - Attempting to set prometheus information withing the rate limit min. /// pub fn do_serve_prometheus( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, version: u32, ip: u128, diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 1e9aaca229..8071570bf8 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -101,7 +101,7 @@ impl Pallet { /// Facilitates user registration of a new subnetwork. /// /// # Args: - /// * 'origin': ('T::RuntimeOrigin'): The calling origin. Must be signed. + /// * 'origin': ('OriginFor'): The calling origin. Must be signed. /// * `identity` (`Option`): Optional identity to be associated with the new subnetwork. /// /// # Event: @@ -115,7 +115,7 @@ impl Pallet { /// * `SubnetIdentityRemoved(netuid)`: Emitted when the identity of a removed network is also deleted. /// pub fn do_register_network( - origin: T::RuntimeOrigin, + origin: OriginFor, hotkey: &T::AccountId, mechid: u16, identity: Option, @@ -333,7 +333,7 @@ impl Pallet { /// # Returns /// /// * `DispatchResult`: A result indicating the success or failure of the operation. - pub fn do_start_call(origin: T::RuntimeOrigin, netuid: NetUid) -> DispatchResult { + pub fn do_start_call(origin: OriginFor, netuid: NetUid) -> DispatchResult { ensure!( Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist @@ -394,7 +394,7 @@ impl Pallet { /// # Rate Limiting /// This function is rate-limited to one call per subnet per interval (e.g., one week). pub fn do_set_sn_owner_hotkey( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, hotkey: &T::AccountId, ) -> DispatchResult { diff --git a/pallets/subtensor/src/subnets/weights.rs b/pallets/subtensor/src/subnets/weights.rs index e234d62be4..0005418b19 100644 --- a/pallets/subtensor/src/subnets/weights.rs +++ b/pallets/subtensor/src/subnets/weights.rs @@ -40,7 +40,7 @@ impl Pallet { /// * `WeightsCommitted`: /// - Emitted upon successfully storing the weight hash. pub fn do_commit_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, commit_hash: H256, ) -> DispatchResult { @@ -138,7 +138,7 @@ impl Pallet { /// - Emitted when the lengths of the input vectors are not equal. /// pub fn do_batch_commit_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuids: Vec>, commit_hashes: Vec, ) -> dispatch::DispatchResult { @@ -228,7 +228,7 @@ impl Pallet { /// * `WeightsCommitted`: /// - Emitted upon successfully storing the weight hash. pub fn do_commit_crv3_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, commit: BoundedVec>, reveal_round: u64, @@ -338,7 +338,7 @@ impl Pallet { /// * `InvalidRevealCommitHashNotMatch`: /// - The revealed hash does not match any committed hash. pub fn do_reveal_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, uids: Vec, values: Vec, @@ -479,7 +479,7 @@ impl Pallet { /// * `InputLengthsUnequal`: /// - The input vectors are of mismatched lengths. pub fn do_batch_reveal_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, uids_list: Vec>, values_list: Vec>, @@ -675,7 +675,7 @@ impl Pallet { /// - Attempting to set weights with max value exceeding limit. /// pub fn do_set_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, uids: Vec, values: Vec, @@ -826,7 +826,7 @@ impl Pallet { /// - Emitted when the lengths of the input vectors are not equal. /// pub fn do_batch_set_weights( - origin: T::RuntimeOrigin, + origin: OriginFor, netuids: Vec>, weights: Vec, Compact)>>, version_keys: Vec>, diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 1b6274db54..0c8363be11 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -26,7 +26,7 @@ impl Pallet { /// * `HotKeyAlreadyRegisteredInSubNet` - If the new hotkey is already registered in the subnet. /// * `NotEnoughBalanceToPaySwapHotKey` - If there is not enough balance to pay for the swap. pub fn do_swap_hotkey( - origin: T::RuntimeOrigin, + origin: OriginFor, old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, netuid: Option, diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 7f0ff7e597..4b17b057f6 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -16,6 +16,8 @@ use frame_support::{ use frame_system as system; use frame_system::{EnsureNever, EnsureRoot, RawOrigin, limits, offchain::CreateTransactionBase}; use pallet_collective::MemberCount; +use pallet_drand::OnPulseReceived; +use pallet_drand::types::RoundNumber; use sp_core::{ConstU64, Get, H256, U256, offchain::KeyTypeId}; use sp_runtime::Perbill; use sp_runtime::{ @@ -38,7 +40,7 @@ frame_support::construct_runtime!( TriumvirateMembers: pallet_membership::::{Pallet, Call, Storage, Event, Config} = 4, Senate: pallet_collective::::{Pallet, Call, Storage, Origin, Event, Config} = 5, SenateMembers: pallet_membership::::{Pallet, Call, Storage, Event, Config} = 6, - SubtensorModule: crate::{Pallet, Call, Storage, Event} = 7, + SubtensorModule: crate::{Pallet, Call, Storage, Event, Origin} = 7, Utility: pallet_utility::{Pallet, Call, Storage, Event} = 8, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 9, Preimage: pallet_preimage::{Pallet, Call, Storage, Event} = 10, @@ -382,7 +384,19 @@ impl pallet_membership::Config for Test { type WeightInfo = pallet_membership::weights::SubstrateWeight; } +pub struct OriginHelper; + +impl EvmOriginHelper for OriginHelper { + fn make_evm_origin(account_id: AccountId) -> RuntimeOrigin { + RuntimeOrigin::from(Origin::Evm { account_id }) + } +} + impl crate::Config for Test { + // type EvmOrigin = EvmOrigin; + type RuntimeOrigin = RuntimeOrigin; + + type EvmOriginHelper = OriginHelper; type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type Currency = Balances; @@ -576,7 +590,7 @@ impl pallet_crowdloan::Config for Test { type MaxContributors = MaxContributors; } -mod test_crypto { +pub(crate) mod test_crypto { use super::KEY_TYPE; use sp_core::{ U256, @@ -610,12 +624,22 @@ mod test_crypto { pub type TestAuthId = test_crypto::TestAuthId; +pub struct StakingJobsOnDrandPulse; +impl OnPulseReceived for StakingJobsOnDrandPulse { + fn on_pulse_received(_: RoundNumber) { + crate::pallet::Pallet::::process_staking_jobs( + frame_system::pallet::Pallet::::block_number(), + ); + } +} + impl pallet_drand::Config for Test { type RuntimeEvent = RuntimeEvent; type AuthorityId = TestAuthId; type Verifier = pallet_drand::verifier::QuicknetVerifier; type UnsignedPriority = ConstU64<{ 1 << 20 }>; type HttpFetchTimeout = ConstU64<1_000>; + type OnPulseReceived = StakingJobsOnDrandPulse; } impl frame_system::offchain::SigningTypes for Test { diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index d80571c594..c9c614cac2 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -2072,3 +2072,374 @@ fn test_swap_stake_limits_destination_netuid() { ))); }); } + +#[test] +fn test_do_move_stake_aggregate_success() { + new_test_ext(1).execute_with(|| { + 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 coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + // Set up initial stake + SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); + SubtensorModule::stake_into_subnet( + &origin_hotkey, + &coldkey, + netuid.into(), + stake_amount, + ::SwapInterface::max_price(), + false, + ) + .unwrap(); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid, + ); + + // Perform the move + let expected_alpha = alpha; + assert_ok!(SubtensorModule::do_move_stake_aggregate( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha, + )); + + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::StakeMoved(..)) + ) + })); + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedStakeMoved { .. }) + ) + })); + + // Check that the stake has been moved + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &origin_hotkey, + &coldkey, + netuid + ), + AlphaCurrency::ZERO + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &destination_hotkey, + &coldkey, + netuid + ), + expected_alpha, + epsilon = expected_alpha / 1000.into() + ); + }); +} + +#[test] +fn test_move_stake_aggregate_fails() { + new_test_ext(1).execute_with(|| { + 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 coldkey = U256::from(1); + let origin_hotkey = U256::from(2); + let destination_hotkey = U256::from(3); + let alpha = 100u64.into(); + + assert_ok!(SubtensorModule::do_move_stake_aggregate( + RuntimeOrigin::signed(coldkey), + origin_hotkey, + destination_hotkey, + netuid, + netuid, + alpha, + )); + + run_to_block_ext(2, true); + + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToMoveAggregatedStake { .. }) + ) + })); + }); +} + +#[test] +fn test_do_transfer_aggregate_success() { + new_test_ext(1).execute_with(|| { + 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 hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); + SubtensorModule::create_account_if_non_existent(&destination_coldkey, &hotkey); + SubtensorModule::stake_into_subnet( + &hotkey, + &origin_coldkey, + netuid, + stake_amount, + ::SwapInterface::max_price(), + false, + ) + .unwrap(); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &origin_coldkey, + netuid, + ); + + let expected_alpha = alpha; + assert_ok!(SubtensorModule::do_transfer_stake_aggregate( + RuntimeOrigin::signed(origin_coldkey), + destination_coldkey, + hotkey, + netuid, + netuid, + alpha + )); + + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::StakeTransferred(..)) + ) + })); + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedStakeTransferred { .. }) + ) + })); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &origin_coldkey, + netuid + ), + AlphaCurrency::ZERO + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &destination_coldkey, + netuid + ), + expected_alpha, + epsilon = expected_alpha / 1000.into() + ); + }); +} + +#[test] +fn test_do_transfer_aggregate_fails() { + new_test_ext(1).execute_with(|| { + 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 hotkey = U256::from(3); + let alpha = 100u64.into(); + + assert_ok!(SubtensorModule::do_transfer_stake_aggregate( + RuntimeOrigin::signed(origin_coldkey), + destination_coldkey, + hotkey, + netuid, + netuid, + alpha + )); + + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToTransferAggregatedStake { .. }) + ) + })); + }); +} + +#[test] +fn test_do_swap_aggregate_success() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let stake_amount = DefaultMinStake::::get() * 10; + + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::stake_into_subnet( + &hotkey, + &coldkey, + origin_netuid, + stake_amount, + ::SwapInterface::max_price(), + false, + ) + .unwrap(); + let alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid, + ); + + let (tao_equivalent, _) = mock::swap_alpha_to_tao_ext(origin_netuid, alpha_before, true); + let (expected_alpha, _) = mock::swap_tao_to_alpha(destination_netuid, tao_equivalent); + assert_ok!(SubtensorModule::do_swap_stake_aggregate( + RuntimeOrigin::signed(coldkey), + hotkey, + origin_netuid, + destination_netuid, + alpha_before, + )); + + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::StakeSwapped(..)) + ) + })); + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedStakeSwapped { .. }) + ) + })); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid + ), + AlphaCurrency::ZERO + ); + + let alpha_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + destination_netuid, + ); + + assert_abs_diff_eq!(alpha_after, expected_alpha, epsilon = 1000.into()); + }); +} + +#[test] +fn test_do_swap_aggregate_fail() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let alpha_before = 100u64.into(); + + assert_ok!(SubtensorModule::do_swap_stake_aggregate( + RuntimeOrigin::signed(coldkey), + hotkey, + origin_netuid, + destination_netuid, + alpha_before, + )); + + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToSwapAggregatedStake { .. }) + ) + })); + }); +} + +#[test] +fn test_swap_stake_limits_succeeds() { + new_test_ext(1).execute_with(|| { + 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 netuid2 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let origin_coldkey = U256::from(1); + let hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); + SubtensorModule::stake_into_subnet( + &hotkey, + &origin_coldkey, + netuid, + stake_amount, + ::SwapInterface::max_price(), + false, + ) + .unwrap(); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &origin_coldkey, + netuid, + ); + + assert_ok!(SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(origin_coldkey), + hotkey, + netuid, + netuid2, + alpha + ),); + + assert!(!StakingOperationRateLimiter::::contains_key(( + hotkey, + origin_coldkey, + netuid + ))); + + assert!(StakingOperationRateLimiter::::contains_key(( + hotkey, + origin_coldkey, + netuid2 + ))); + }); +} \ No newline at end of file diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 08dade7ef3..7cd17ae032 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -6,12 +6,17 @@ use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays use frame_support::sp_runtime::{ DispatchError, traits::TxBaseImplication, transaction_validity::TransactionSource, }; +use frame_support::traits::Randomness; use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; use frame_system::RawOrigin; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_drand::types::DrandResponseBody; +use pallet_drand::types::Pulse; +use pallet_drand::types::PulsesPayload; use pallet_subtensor_swap::Call as SwapCall; use pallet_subtensor_swap::tick::TickIndex; use safe_math::FixedExt; -use sp_core::{Get, H256, U256}; +use sp_core::{Get, H256, Pair, U256, blake2_128}; use substrate_fixed::traits::FromFixed; use substrate_fixed::types::{I96F32, I110F18, U64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency as CurrencyT, NetUid}; @@ -5766,3 +5771,1476 @@ fn test_stake_rate_limits() { ))); }); } + +#[test] +fn test_add_stake_aggregate_ok_no_emission() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55453); + let amount = DefaultMinStake::::get() * 10; + + //add network + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + + // Give it some $$$ in his coldkey balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); + + // Check we have zero staked before transfer + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), + 0 + ); + + // Also total stake should be equal to the network initial lock + assert_eq!( + SubtensorModule::get_total_stake(), + SubtensorModule::get_network_min_lock() + ); + + // Transfer to hotkey account, and check if the result is ok + assert_ok!(SubtensorModule::add_stake_aggregate( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount + )); + + // Ensure that extrinsic call doesn't change the stake. + assert_eq!( + SubtensorModule::get_total_stake(), + SubtensorModule::get_network_min_lock() + ); + + // Check for the block delay + run_to_block_ext(2, true); + + let approx_fee = + ::SwapInterface::approx_fee_amount(netuid.into(), amount); + + // Check if stake has increased + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), + amount + approx_fee, + epsilon = amount / 1000, + ); + + // Check if balance has decreased + assert_eq!(SubtensorModule::get_coldkey_balance(&coldkey_account_id), 1); + + // Check if total stake has increased accordingly. + assert_eq!( + SubtensorModule::get_total_stake(), + amount + SubtensorModule::get_network_min_lock() + ); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::StakeAdded { .. }) + ) + })); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedStakeAdded { .. }) + ) + })); + }); +} + +#[test] +fn test_add_stake_aggregate_ok_with_drand_pulse() { + let block_number = 1u64; + new_test_ext(block_number).execute_with(|| { + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55453); + let amount = DefaultMinStake::::get() * 10; + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); + + // Transfer to hotkey account, and check if the result is ok + assert_ok!(SubtensorModule::add_stake_aggregate( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount + )); + + const DRAND_PULSE: &str = "{\"round\":1000,\"randomness\":\"fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd\",\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}"; + let u_p: DrandResponseBody = serde_json::from_str(DRAND_PULSE).unwrap(); + let p: Pulse = u_p.try_into_pulse().unwrap(); + let alice = super::mock::test_crypto::Pair::from_string("//Alice", None).expect("static values are valid"); + let signature = None; + + let pulses_payload = PulsesPayload { + pulses: vec![p.clone()], + block_number, + public: alice.public(), + }; + + // Dispatch an unsigned extrinsic. + assert_ok!(Drand::write_pulse( + RuntimeOrigin::none(), + pulses_payload, + signature + )); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::StakeAdded { .. }) + ) + })); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedStakeAdded { .. }) + ) + })); + }); +} + +#[test] +fn test_add_stake_aggregate_failed() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55453); + let amount = DefaultMinStake::::get() * 100; + //add network + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + + // Transfer to hotkey account, and check if the result is ok + assert_ok!(SubtensorModule::add_stake_aggregate( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount + )); + + // Enable on_finalize code to run + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToAddAggregatedStake { .. }) + ) + })); + }); +} +#[test] +fn test_add_stake_aggregate_limit_ok() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55453); + let amount = 900_000_000_000; // over the maximum + + // add network + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + + // Forse-set alpha in and tao reserve to make price equal 1.5 + let tao_reserve = U96F32::from_num(150_000_000_000_u64); + let alpha_in = AlphaCurrency::from(100_000_000_000_u64); + mock::setup_reserves(netuid, tao_reserve.to_num(), alpha_in); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); + assert_eq!(current_price, U96F32::from_num(1.5)); + + // Give it some $$$ in his coldkey balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); + + // Setup limit price so that it doesn't peak above 4x of current price + // The amount that can be executed at this price is 450 TAO only + // Alpha produced will be equal to 75 = 450*100/(450+150) + let limit_price = 24_000_000_000; + let expected_executed_stake = AlphaCurrency::from(75_000_000_000); + + // Add stake with slippage safety and check if the result is ok + assert_ok!(SubtensorModule::add_stake_limit_aggregate( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount, + limit_price, + true + )); + + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::StakeAdded(..)) + ) + })); + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedLimitedStakeAdded { .. }) + ) + })); + + // Check if stake has increased only by 75 Alpha + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid + ), + expected_executed_stake, + epsilon = expected_executed_stake / 1000.into(), + ); + + // Check that 450 TAO less fees balance still remains free on coldkey + let fee = ::SwapInterface::approx_fee_amount( + netuid.into(), + amount / 2, + ) as f64; + assert_abs_diff_eq!( + SubtensorModule::get_coldkey_balance(&coldkey_account_id), + amount / 2 - fee as u64, + epsilon = amount / 2 / 1000 + ); + + // Check that price has updated to ~24 = (150+450) / (100 - 75) + let exp_price = U96F32::from_num(24.0); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); + assert_abs_diff_eq!( + exp_price.to_num::(), + current_price.to_num::(), + epsilon = 0.001, + ); + }); +} + +#[test] +fn test_add_stake_limit_aggregate_fail() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55453); + let amount = 900_000_000_000; + let limit_price = 6_000_000_000; + // add network + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + + assert_ok!(SubtensorModule::add_stake_limit_aggregate( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount, + limit_price, + true + )); + + // Check that event was not emitted. + assert!(System::events().iter().all(|e| { + !matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToAddAggregatedLimitedStake { .. }) + ) + })); + + // Check for the block delay + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToAddAggregatedLimitedStake { .. }) + ) + })); + }); +} + +#[test] +fn test_remove_stake_aggregate_ok_no_emission() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1); + let subnet_owner_hotkey = U256::from(2); + let coldkey_account_id = U256::from(4343); + let hotkey_account_id = U256::from(4968585); + let amount = DefaultMinStake::::get() * 10; + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); + + // Some basic assertions + assert_eq!( + SubtensorModule::get_total_stake(), + SubtensorModule::get_network_min_lock() + ); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), + 0 + ); + assert_eq!(SubtensorModule::get_coldkey_balance(&coldkey_account_id), 0); + + // Give the neuron some stake to remove + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + amount.into(), + ); + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), + amount, + epsilon = amount / 1000 + ); + + // Add subnet TAO for the equivalent amount added at price + let (amount_tao, fee) = mock::swap_alpha_to_tao(netuid, amount.into()); + SubnetTAO::::mutate(netuid, |v| *v += amount_tao + fee); + TotalStake::::mutate(|v| *v += amount_tao + fee); + + // Do the magic + assert_ok!(SubtensorModule::remove_stake_aggregate( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount.into() + )); + + // Check for the block delay + run_to_block_ext(2, true); + + // we do not expect the exact amount due to slippage + assert!(SubtensorModule::get_coldkey_balance(&coldkey_account_id) > amount / 10 * 9 - fee); + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), + 0, + epsilon = 20000 + ); + assert_abs_diff_eq!( + SubtensorModule::get_total_stake(), + SubtensorModule::get_network_min_lock() + fee, + epsilon = SubtensorModule::get_total_stake() / 100_000 + ); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::StakeRemoved(..)) + ) + })); + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedStakeRemoved { .. }) + ) + })); + }); +} + +#[test] +fn test_remove_stake_aggregate_fail() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1); + let subnet_owner_hotkey = U256::from(2); + let coldkey_account_id = U256::from(4343); + let hotkey_account_id = U256::from(4968585); + let amount = DefaultMinStake::::get() * 10; + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); + + assert_ok!(SubtensorModule::remove_stake_aggregate( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount.into() + )); + + // Check that event was not emitted. + assert!(System::events().iter().all(|e| { + !matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToRemoveAggregatedStake { .. }) + ) + })); + + // Enable on_finalize code to run + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToRemoveAggregatedStake { .. }) + ) + })); + }); +} + +#[test] +fn test_remove_stake_limit_aggregate_ok() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55453); + let stake_amount = 300_000_000_000; + + // add network + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey_account_id, + stake_amount + ExistentialDeposit::get(), + ); + + // Forse-set sufficient reserves + let tao_reserve: U96F32 = U96F32::from_num(100_000_000_000_u64); + let alpha_in = AlphaCurrency::from(100_000_000_000); + SubnetTAO::::insert(netuid, tao_reserve.to_num::()); + SubnetAlphaIn::::insert(netuid, alpha_in); + + // Stake to hotkey account, and check if the result is ok + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + stake_amount + )); + let alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + ); + + // Setup limit price to 99% of current price + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); + let limit_price = (current_price.to_num::() * 990_000_000_f64) as u64; + + // Alpha unstaked - calculated using formula from delta_in() + let expected_alpha_reduction = (0.00138 * (alpha_in.to_u64() as f64)) as u64; + let fee: u64 = (expected_alpha_reduction as f64 * 0.003) as u64; + + // Remove stake with slippage safety + remove_stake_rate_limit_for_tests(&hotkey_account_id, &coldkey_account_id, netuid); + assert_ok!(SubtensorModule::remove_stake_limit_aggregate( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + alpha_before / 2.into(), + limit_price, + true + )); + + // Enable on_finalize code to run + run_to_block_ext(2, true); + + let alpha_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + ); + + // Check if stake has decreased properly + assert_abs_diff_eq!( + alpha_before - alpha_after, + AlphaCurrency::from(expected_alpha_reduction + fee), + epsilon = AlphaCurrency::from(expected_alpha_reduction / 10), + ); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::StakeRemoved(..)) + ) + })); + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedLimitedStakeRemoved { .. }) + ) + })); + }); +} + +#[test] +fn test_remove_stake_limit_aggregate_fail() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55453); + let stake_amount = 300_000_000; + let unstake_amount = 150_000_000_000; + let limit_price = 1_350_000_000; + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + + // Give the neuron some stake to remove + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + stake_amount.into(), + ); + + assert_ok!(SubtensorModule::remove_stake_limit_aggregate( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + unstake_amount.into(), + limit_price, + true + )); + + // Check that event was not emitted. + assert!(System::events().iter().all(|e| { + !matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToRemoveAggregatedLimitedStake { .. }) + ) + })); + + // Enable on_finalize code to run + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToRemoveAggregatedLimitedStake { .. }) + ) + })); + }); +} + +#[test] +fn test_unstake_all_aggregate_works() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let coldkey = U256::from(1); + let hotkey = U256::from(2); + + let stake_amount = 190_000_000_000; // 190 TAO + + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + register_ok_neuron(netuid, hotkey, coldkey, 192213123); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey, + stake_amount + ExistentialDeposit::get(), + ); + + // Give the neuron some stake to remove + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + stake_amount + )); + + // Setup the pool so that removing all the TAO will keep liq above min + mock::setup_reserves( + netuid, + stake_amount * 10, + AlphaCurrency::from(stake_amount * 100), + ); + remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid); + + // Unstake all alpha to free balance + assert_ok!(SubtensorModule::unstake_all_aggregate( + RuntimeOrigin::signed(coldkey), + hotkey, + )); + + // Check for the block delay + run_to_block_ext(2, true); + + let new_alpha = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + assert_abs_diff_eq!(new_alpha, AlphaCurrency::ZERO, epsilon = 1_000.into()); + let new_balance = SubtensorModule::get_coldkey_balance(&coldkey); + assert!(new_balance > 100_000); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedUnstakeAllSucceeded { .. }) + ) + })); + }); +} + +#[test] +fn test_unstake_all_aggregate_fails() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + + // Unstake all alpha to root + assert_ok!(SubtensorModule::unstake_all_aggregate( + RuntimeOrigin::signed(coldkey), + hotkey, + )); + + // Check that event was not emitted. + assert!(System::events().iter().all(|e| { + !matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedUnstakeAllFailed { .. }) + ) + })); + + // Enable on_finalize code to run + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedUnstakeAllFailed { .. }) + ) + })); + }); +} + +#[test] +fn test_unstake_all_alpha_aggregate_works() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let coldkey = U256::from(1); + let hotkey = U256::from(2); + + let stake_amount = 190_000_000_000; // 190 TAO + + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + register_ok_neuron(netuid, hotkey, coldkey, 192213123); + SubtensorModule::add_balance_to_coldkey_account( + &coldkey, + stake_amount + ExistentialDeposit::get(), + ); + + // Give the neuron some stake to remove + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + stake_amount + )); + + remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid); + + // Setup the pool so that removing all the TAO will keep liq above min + mock::setup_reserves(netuid, stake_amount * 10, (stake_amount * 100).into()); + + // Unstake all alpha to root + assert_ok!(SubtensorModule::unstake_all_alpha_aggregate( + RuntimeOrigin::signed(coldkey), + hotkey, + )); + + // Check for the block delay + run_to_block_ext(2, true); + + let new_alpha = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + assert_abs_diff_eq!(new_alpha, AlphaCurrency::ZERO, epsilon = 1_000.into()); + let new_root = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + NetUid::ROOT, + ); + assert!(new_root > 100_000.into()); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedUnstakeAllAlphaSucceeded { .. }) + ) + })); + }); +} + +#[test] +fn test_unstake_all_alpha_aggregate_fails() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + + assert_ok!(SubtensorModule::unstake_all_alpha_aggregate( + RuntimeOrigin::signed(coldkey), + hotkey, + )); + + // Check that event was not emitted. + assert!(System::events().iter().all(|e| { + !matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedUnstakeAllAlphaFailed { .. }) + ) + })); + + // Enable on_finalize code to run + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedUnstakeAllAlphaFailed { .. }) + ) + })); + }); +} + +#[test] +fn test_remove_stake_full_limit_aggregate_ok() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(1); + let coldkey_account_id = U256::from(2); + let stake_amount = AlphaCurrency::from(10_000_000_000); + + // add network + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + + // Give the neuron some stake to remove + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid, + stake_amount, + ); + + let tao_reserve: U96F32 = U96F32::from_num(100_000_000_000_u64); + let alpha_in = AlphaCurrency::from(100_000_000_000); + SubnetTAO::::insert(netuid, tao_reserve.to_num::()); + SubnetAlphaIn::::insert(netuid, alpha_in); + + let limit_price = 90_000_000; + + // Remove stake with slippage safety + assert_ok!(SubtensorModule::remove_stake_full_limit_aggregate( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + Some(limit_price), + )); + + // Check that event was not emitted. + assert!(!System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::StakeRemoved(..)) + ) + })); + + // Enable on_finalize code to run + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::StakeRemoved(..)) + ) + })); + + // Check if stake has decreased to zero + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey_account_id, + &coldkey_account_id, + netuid + ), + AlphaCurrency::ZERO + ); + + let new_balance = SubtensorModule::get_coldkey_balance(&coldkey_account_id); + assert_abs_diff_eq!(new_balance, 9_086_000_000, epsilon = 1_000_000); + }); +} + +#[test] +#[allow(clippy::indexing_slicing)] +fn test_verify_all_job_type_sort_by_coldkey() { + fn run_by_pulse(pulse: &str) { + new_test_ext(1).execute_with(|| { + let amount = 1_000_000_000_000; + let alpha: AlphaCurrency = 1_000_000_000_000u64.into(); + let limit_price = 6_000_000_000u64; + + // Coldkeys and hotkeys + let coldkeys = vec![ + U256::from(100), // add_stake + U256::from(200), // add_stake + U256::from(300), // add_stake_limit + U256::from(400), // add_stake_limit + U256::from(500), // remove_stake + U256::from(600), // remove_stake + U256::from(700), // remove_stake_limit + U256::from(800), // remove_stake_limit + U256::from(900), // unstake_all + U256::from(1000), // unstake_all + U256::from(1100), // unstake_all_alpha + U256::from(1200), // unstake_all_alpha + U256::from(1300), // swap_stake + U256::from(1400), // swap_stake + U256::from(1500), // move_stake + U256::from(1600), // move_stake + U256::from(1700), // transfer_stake + U256::from(1800), // transfer_stake + U256::from(1900), // swap_stake_limit + U256::from(2000), // swap_stake_limit + ]; + + let coldkey_count = coldkeys.len(); + let hotkeys = (1..=coldkey_count).map(U256::from).collect::>(); + + let netuids: Vec<_> = hotkeys + .iter() + .zip(coldkeys.iter()) + .map(|(h, c)| add_dynamic_network(h, c)) + .collect(); + + let (swap_stake_destination_uid1, swap_stake_destination_uid2) = { + let swap_coldkey1 = U256::from(1300); + let swap_coldkey2 = U256::from(1301); + + let swap_hotkey1 = U256::from(13000); + let swap_hotkey2 = U256::from(13001); + + let netuid1 = add_dynamic_network(&swap_coldkey1, &swap_hotkey1); + let netuid2 = add_dynamic_network(&swap_coldkey2, &swap_hotkey2); + + (netuid1, netuid2) + }; + + let (swap_stake_limit_destination_uid1, swap_stake_limit_destination_uid2) = { + let swap_coldkey1 = U256::from(1310); + let swap_coldkey2 = U256::from(1311); + + let swap_hotkey1 = U256::from(13010); + let swap_hotkey2 = U256::from(13011); + + let netuid1 = add_dynamic_network(&swap_coldkey1, &swap_hotkey1); + let netuid2 = add_dynamic_network(&swap_coldkey2, &swap_hotkey2); + + (netuid1, netuid2) + }; + + let ( + move_stake_destination_uid1, + move_stake_destination_uid2, + move_stake_destination_hotkey1, + move_stake_destination_hotkey2, + ) = { + let extra_coldkey1 = U256::from(1400); + let extra_coldkey2 = U256::from(1401); + + let extra_hotkey1 = U256::from(14000); + let extra_hotkey2 = U256::from(14001); + + let netuid1 = add_dynamic_network(&extra_coldkey1, &extra_hotkey1); + let netuid2 = add_dynamic_network(&extra_coldkey2, &extra_hotkey2); + + SubtensorModule::create_account_if_non_existent(&extra_coldkey1, &extra_hotkey1); + SubtensorModule::create_account_if_non_existent(&extra_coldkey2, &extra_hotkey2); + + (netuid1, netuid2, extra_hotkey1, extra_hotkey2) + }; + + let ( + transfer_stake_destination_uid1, + transfer_stake_destination_uid2, + transfer_stake_destination_coldkey1, + transfer_stake_destination_coldkey2, + ) = { + let extra_coldkey1 = U256::from(1400); + let extra_coldkey2 = U256::from(1401); + + let extra_hotkey1 = U256::from(14000); + let extra_hotkey2 = U256::from(14001); + + let netuid1 = add_dynamic_network(&extra_coldkey1, &extra_hotkey1); + let netuid2 = add_dynamic_network(&extra_coldkey2, &extra_hotkey2); + + SubtensorModule::add_balance_to_coldkey_account(&extra_coldkey1, amount); + SubtensorModule::add_balance_to_coldkey_account(&extra_coldkey2, amount); + + SubtensorModule::create_account_if_non_existent(&extra_coldkey1, &extra_hotkey1); + SubtensorModule::create_account_if_non_existent(&extra_coldkey2, &extra_hotkey2); + + (netuid1, netuid2, extra_coldkey1, extra_coldkey2) + }; + + let tao_reserve = U96F32::from_num(150_000_000_000u64); + let alpha_in: AlphaCurrency = + U96F32::from_num(100_000_000_000u64).to_num::().into(); + + for netuid in &netuids { + SubnetTAO::::insert(*netuid, tao_reserve.to_num::()); + SubnetAlphaIn::::insert(*netuid, alpha_in); + } + + let extra_netuids = vec![ + swap_stake_destination_uid1, + swap_stake_destination_uid2, + move_stake_destination_uid1, + move_stake_destination_uid2, + transfer_stake_destination_uid1, + transfer_stake_destination_uid2, + swap_stake_limit_destination_uid1, + swap_stake_limit_destination_uid2, + ]; + for netuid in &extra_netuids { + SubnetTAO::::insert(*netuid, tao_reserve.to_num::()); + SubnetAlphaIn::::insert(*netuid, alpha_in); + } + + for coldkey in &coldkeys { + SubtensorModule::add_balance_to_coldkey_account(coldkey, amount); + } + + for ((hotkey, coldkey), netuid) in + hotkeys.iter().zip(coldkeys.iter()).zip(netuids.iter()) + { + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + hotkey, + coldkey, + *netuid, + amount.into(), + ); + } + + // === Pulse fired === + let u_p: DrandResponseBody = serde_json::from_str(pulse).unwrap(); + let pulse: Pulse = u_p.try_into_pulse().unwrap(); + let alice = super::mock::test_crypto::Pair::from_string("//Alice", None) + .expect("static values are valid"); + let signature = None; + + let pulses_payload = PulsesPayload { + pulses: vec![pulse.clone()], + block_number: 1u64, + public: alice.public(), + }; + + // Dispatch an unsigned extrinsic. + assert_ok!(Drand::write_pulse( + RuntimeOrigin::none(), + pulses_payload, + signature + )); + + // === Submit all job types === + + assert_ok!(SubtensorModule::add_stake_aggregate( + RuntimeOrigin::signed(coldkeys[0]), + hotkeys[0], + netuids[0], + amount + )); + assert_ok!(SubtensorModule::add_stake_aggregate( + RuntimeOrigin::signed(coldkeys[1]), + hotkeys[1], + netuids[1], + amount + )); + + assert_ok!(SubtensorModule::add_stake_limit_aggregate( + RuntimeOrigin::signed(coldkeys[2]), + hotkeys[2], + netuids[2], + amount, + limit_price, + true + )); + assert_ok!(SubtensorModule::add_stake_limit_aggregate( + RuntimeOrigin::signed(coldkeys[3]), + hotkeys[3], + netuids[3], + amount, + limit_price, + true + )); + + assert_ok!(SubtensorModule::remove_stake_aggregate( + RuntimeOrigin::signed(coldkeys[4]), + hotkeys[4], + netuids[4], + alpha + )); + assert_ok!(SubtensorModule::remove_stake_aggregate( + RuntimeOrigin::signed(coldkeys[5]), + hotkeys[5], + netuids[5], + alpha + )); + + fn remove_limt_price_helper( + netuid: NetUid, + hotkey: &U256, + coldkey: &U256, + ) -> (AlphaCurrency, u64) { + let stake_amount = 300_000_000_000; + + // Forse-set sufficient reserves + let tao_reserve: U96F32 = U96F32::from_num(100_000_000_000_u64); + let alpha_in = AlphaCurrency::from(100_000_000_000); + SubnetTAO::::insert(netuid, tao_reserve.to_num::()); + SubnetAlphaIn::::insert(netuid, alpha_in); + + // Stake to hotkey account, and check if the result is ok + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(*coldkey), + *hotkey, + netuid, + stake_amount + )); + + // Setup limit price to 99% of current price + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); + let remove_limit_price = (current_price.to_num::() * 990_000_000_f64) as u64; + let alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + hotkey, coldkey, netuid, + ); + let remove_alpha = alpha_before / 2.into(); + + // Remove stake with slippage safety + remove_stake_rate_limit_for_tests(hotkey, coldkey, netuid); + + (remove_alpha, remove_limit_price) + } + + let (remove_alpha1, remove_limit_price1) = + remove_limt_price_helper(netuids[6], &hotkeys[6], &coldkeys[6]); + assert_ok!(SubtensorModule::remove_stake_limit_aggregate( + RuntimeOrigin::signed(coldkeys[6]), + hotkeys[6], + netuids[6], + remove_alpha1, + remove_limit_price1, + true + )); + + let (remove_alpha2, remove_limit_price2) = + remove_limt_price_helper(netuids[7], &hotkeys[7], &coldkeys[7]); + + assert_ok!(SubtensorModule::remove_stake_limit_aggregate( + RuntimeOrigin::signed(coldkeys[7]), + hotkeys[7], + netuids[7], + remove_alpha2, + remove_limit_price2, + true + )); + + assert_ok!(SubtensorModule::unstake_all_aggregate( + RuntimeOrigin::signed(coldkeys[8]), + hotkeys[8], + )); + assert_ok!(SubtensorModule::unstake_all_aggregate( + RuntimeOrigin::signed(coldkeys[9]), + hotkeys[9], + )); + + assert_ok!(SubtensorModule::unstake_all_alpha_aggregate( + RuntimeOrigin::signed(coldkeys[10]), + hotkeys[10], + )); + assert_ok!(SubtensorModule::unstake_all_alpha_aggregate( + RuntimeOrigin::signed(coldkeys[11]), + hotkeys[11], + )); + + assert_ok!(SubtensorModule::swap_stake_aggregate( + RuntimeOrigin::signed(coldkeys[12]), + hotkeys[12], + netuids[12], + swap_stake_destination_uid1.into(), + alpha + )); + + assert_ok!(SubtensorModule::swap_stake_aggregate( + RuntimeOrigin::signed(coldkeys[13]), + hotkeys[13], + netuids[13], + swap_stake_destination_uid2.into(), + alpha + )); + + assert_ok!(SubtensorModule::move_stake_aggregate( + RuntimeOrigin::signed(coldkeys[14]), + hotkeys[14], + move_stake_destination_hotkey1, + netuids[14], + move_stake_destination_uid1.into(), + alpha + )); + + assert_ok!(SubtensorModule::move_stake_aggregate( + RuntimeOrigin::signed(coldkeys[15]), + hotkeys[15], + move_stake_destination_hotkey2, + netuids[15], + move_stake_destination_uid2.into(), + alpha + )); + + assert_ok!(SubtensorModule::transfer_stake_aggregate( + RuntimeOrigin::signed(coldkeys[16]), + transfer_stake_destination_coldkey1, + hotkeys[16], + netuids[16], + transfer_stake_destination_uid1.into(), + alpha + )); + + assert_ok!(SubtensorModule::transfer_stake_aggregate( + RuntimeOrigin::signed(coldkeys[17]), + transfer_stake_destination_coldkey2, + hotkeys[17], + netuids[17], + transfer_stake_destination_uid2.into(), + alpha, + )); + + fn swap_stake_limit_helper( + origin_netuid: NetUid, + destination_netuid: NetUid, + hotkey: U256, coldkey: U256 + ) -> (AlphaCurrency, u64) { + let stake_amount = AlphaCurrency::from(150_000_000_000); + let move_amount = AlphaCurrency::from(150_000_000_000); + let limit_price = 990_000_000u64; + + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid, + stake_amount, + ); + + let tao_reserve: U96F32 = U96F32::from_num(150_000_000_000_u64); + let alpha_in = AlphaCurrency::from(100_000_000_000); + SubnetTAO::::insert(origin_netuid, tao_reserve.to_num::()); + SubnetAlphaIn::::insert(origin_netuid, alpha_in); + SubnetTAO::::insert(destination_netuid, (tao_reserve * 100_000).to_num::()); + SubnetAlphaIn::::insert(destination_netuid, alpha_in * 100_000.into()); + + (move_amount, limit_price) + } + + let (swap_stake_limit_alpha, limit_price) = swap_stake_limit_helper( + netuids[18], + swap_stake_limit_destination_uid1.into(), + hotkeys[18], + coldkeys[18] + ); + + assert_ok!(SubtensorModule::swap_stake_limit_aggregate( + RuntimeOrigin::signed(coldkeys[18]), + hotkeys[18], + netuids[18], + swap_stake_limit_destination_uid1.into(), + swap_stake_limit_alpha, + limit_price, + true + )); + + let (swap_stake_limit_alpha, limit_price) = swap_stake_limit_helper( + netuids[19], + swap_stake_limit_destination_uid2.into(), + hotkeys[19], + coldkeys[19] + ); + + assert_ok!(SubtensorModule::swap_stake_limit_aggregate( + RuntimeOrigin::signed(coldkeys[19]), + hotkeys[19], + netuids[19], + swap_stake_limit_destination_uid1.into(), + swap_stake_limit_alpha, + limit_price, + true + )); + + // Finalize block + run_to_block_ext(2, true); + + // === Collect coldkeys by event type === + let mut add_coldkeys = vec![]; + let mut add_limit_coldkeys = vec![]; + let mut remove_coldkeys = vec![]; + let mut remove_limit_coldkeys = vec![]; + let mut unstake_all_coldkeys = vec![]; + let mut unstake_all_alpha_coldkeys = vec![]; + let mut swap_stake_coldkeys = vec![]; + let mut move_stake_coldkeys = vec![]; + let mut transfer_stake_coldkeys = vec![]; + let mut swap_stake_limit_coldkeys = vec![]; + + for event in System::events().iter().map(|e| &e.event) { + match event { + RuntimeEvent::SubtensorModule(Event::AggregatedStakeAdded { + coldkey, .. + }) => { + add_coldkeys.push(*coldkey); + } + RuntimeEvent::SubtensorModule(Event::AggregatedLimitedStakeAdded { + coldkey, + .. + }) => { + add_limit_coldkeys.push(*coldkey); + } + RuntimeEvent::SubtensorModule(Event::AggregatedStakeRemoved { + coldkey, + .. + }) => { + remove_coldkeys.push(*coldkey); + } + RuntimeEvent::SubtensorModule(Event::AggregatedLimitedStakeRemoved { + coldkey, + .. + }) => { + remove_limit_coldkeys.push(*coldkey); + } + RuntimeEvent::SubtensorModule(Event::AggregatedUnstakeAllSucceeded { + coldkey, + .. + }) => { + unstake_all_coldkeys.push(*coldkey); + } + RuntimeEvent::SubtensorModule(Event::AggregatedUnstakeAllAlphaSucceeded { + coldkey, + .. + }) => { + unstake_all_alpha_coldkeys.push(*coldkey); + } + RuntimeEvent::SubtensorModule(Event::AggregatedStakeSwapped { + coldkey, + .. + }) => { + swap_stake_coldkeys.push(*coldkey); + } + RuntimeEvent::SubtensorModule(Event::AggregatedStakeMoved { + coldkey, .. + }) => { + move_stake_coldkeys.push(*coldkey); + } + RuntimeEvent::SubtensorModule(Event::AggregatedStakeTransferred { + origin_coldkey, + .. + }) => { + transfer_stake_coldkeys.push(*origin_coldkey); + } + RuntimeEvent::SubtensorModule(Event::AggregatedLimitedStakeSwapped { + coldkey, + .. + }) => { + swap_stake_limit_coldkeys.push(*coldkey); + } + _ => {} + } + } + + // === Assertions === + let direct_order = { + let (randomness, _) = as Randomness< + _, + BlockNumberFor, + >>::random(b"staking_ops"); + let hash = blake2_128(randomness.as_ref()); + let first_byte = hash.as_ref().first().unwrap_or(&0); + + // Extract the first bit + (*first_byte & 0b10000000) == 0 + }; + + if direct_order { + assert_eq!(add_coldkeys, vec![coldkeys[0], coldkeys[1]]); + assert_eq!(add_limit_coldkeys, vec![coldkeys[2], coldkeys[3]]); + assert_eq!(remove_coldkeys, vec![coldkeys[4], coldkeys[5]]); + assert_eq!(remove_limit_coldkeys, vec![coldkeys[6], coldkeys[7]]); + assert_eq!(unstake_all_coldkeys, vec![coldkeys[8], coldkeys[9]]); + assert_eq!(unstake_all_alpha_coldkeys, vec![coldkeys[10], coldkeys[11]]); + assert_eq!(swap_stake_coldkeys, vec![coldkeys[12], coldkeys[13]]); + assert_eq!(move_stake_coldkeys, vec![coldkeys[14], coldkeys[15]]); + assert_eq!(transfer_stake_coldkeys, vec![coldkeys[16], coldkeys[17]]); + assert_eq!(swap_stake_limit_coldkeys, vec![coldkeys[18], coldkeys[19]]); + } else { + // reverse order + assert_eq!(add_coldkeys, vec![coldkeys[1], coldkeys[0]]); + assert_eq!(add_limit_coldkeys, vec![coldkeys[3], coldkeys[2]]); + assert_eq!(remove_coldkeys, vec![coldkeys[5], coldkeys[4]]); + assert_eq!(remove_limit_coldkeys, vec![coldkeys[7], coldkeys[6]]); + assert_eq!(unstake_all_coldkeys, vec![coldkeys[9], coldkeys[8]]); + assert_eq!(unstake_all_alpha_coldkeys, vec![coldkeys[11], coldkeys[10]]); + assert_eq!(swap_stake_coldkeys, vec![coldkeys[13], coldkeys[12]]); + assert_eq!(move_stake_coldkeys, vec![coldkeys[15], coldkeys[14]]); + assert_eq!(transfer_stake_coldkeys, vec![coldkeys[17], coldkeys[16]]); + assert_eq!(swap_stake_limit_coldkeys, vec![coldkeys[19], coldkeys[18]]); + } + }); + } + + const DRAND_PULSE1: &str = "{\"round\":1000,\"randomness\":\"fe290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd\",\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}"; + const DRAND_PULSE2: &str = "{\"round\":1000,\"randomness\":\"1e290beca10872ef2fb164d2aa4442de4566183ec51c56ff3cd603d930e54fdd\",\"signature\":\"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39\"}"; + + run_by_pulse(DRAND_PULSE1); // direct order + run_by_pulse(DRAND_PULSE2); // reversed +} + +#[test] +fn test_add_stake_evm_origin_check() { + new_test_ext(1).execute_with(|| { + let hotkey_account_id = U256::from(533453); + let coldkey_account_id = U256::from(55453); + let amount = DefaultMinStake::::get() * 10; + + //add network + let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); + + setup_reserves(netuid, amount * 1_000_000, (amount * 10_000_000).into()); + + // Give it some $$$ in his coldkey balance + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); + + assert_err!( + SubtensorModule::add_stake_evm( + RuntimeOrigin::signed(coldkey_account_id), + hotkey_account_id, + netuid, + amount + ), + DispatchError::BadOrigin + ); + + assert_ok!(SubtensorModule::add_stake_evm( + ::EvmOriginHelper::make_evm_origin(coldkey_account_id).into(), + hotkey_account_id, + netuid, + amount + )); + }); +} + +#[test] +fn test_swap_stake_limit_aggregate_ok() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let stake_amount = AlphaCurrency::from(150_000_000_000); + let move_amount = AlphaCurrency::from(150_000_000_000); + + // add network + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + register_ok_neuron(origin_netuid, hotkey, coldkey, 192213123); + register_ok_neuron(destination_netuid, hotkey, coldkey, 192213123); + + // Give the neuron some stake to remove + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid, + stake_amount, + ); + + // Forse-set alpha in and tao reserve to make price equal 1.5 on both origin and destination, + // but there's much more liquidity on destination, so its price wouldn't go up when restaked + let tao_reserve: U96F32 = U96F32::from_num(150_000_000_000_u64); + let alpha_in = AlphaCurrency::from(100_000_000_000); + SubnetTAO::::insert(origin_netuid, tao_reserve.to_num::()); + SubnetAlphaIn::::insert(origin_netuid, alpha_in); + SubnetTAO::::insert(destination_netuid, (tao_reserve * 100_000).to_num::()); + SubnetAlphaIn::::insert(destination_netuid, alpha_in * 100_000.into()); + let current_price = + ::SwapInterface::current_alpha_price(origin_netuid.into()); + assert_eq!(current_price, U96F32::from_num(1.5)); + + // The relative price between origin and destination subnets is 1. + // Setup limit relative price so that it doesn't drop by more than 1% from current price + let limit_price = 990_000_000; + + // Move stake with slippage safety - executes partially + assert_ok!(SubtensorModule::swap_stake_limit_aggregate( + RuntimeOrigin::signed(coldkey), + hotkey, + origin_netuid, + destination_netuid, + move_amount, + limit_price, + true, + )); + + // Check for the block delay + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::AggregatedLimitedStakeSwapped { .. }) + ) + })); + + let new_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid, + ); + + assert_abs_diff_eq!( + new_alpha, + AlphaCurrency::from(149_000_000_000), + epsilon = 100_000_000.into() + ); + }); +} + +#[test] +fn test_swap_stake_limit_aggregate_fail() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let move_amount = AlphaCurrency::from(150_000_000_000); + let limit_price = 990_000_000; + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + // Move stake with slippage safety - executes partially + assert_ok!(SubtensorModule::swap_stake_limit_aggregate( + RuntimeOrigin::signed(coldkey), + hotkey, + origin_netuid, + destination_netuid, + move_amount, + limit_price, + true, + )); + + // Check for the block delay + run_to_block_ext(2, true); + + // Check that event was emitted. + assert!(System::events().iter().any(|e| { + matches!( + &e.event, + RuntimeEvent::SubtensorModule(Event::FailedToSwapLimitedAggregatedStake{ .. }) + ) + })); + }); +} \ No newline at end of file diff --git a/pallets/subtensor/src/utils/evm.rs b/pallets/subtensor/src/utils/evm.rs index 652fb8ea27..28044d1a8a 100644 --- a/pallets/subtensor/src/utils/evm.rs +++ b/pallets/subtensor/src/utils/evm.rs @@ -42,7 +42,7 @@ impl Pallet { /// * `block_number` - The block number used in the `signature`. /// * `signature` - A signed message by the `evm_key` containing the `hotkey` and the hashed `block_number`. pub fn do_associate_evm_key( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, evm_key: H160, block_number: u64, diff --git a/pallets/subtensor/src/utils/identity.rs b/pallets/subtensor/src/utils/identity.rs index 059fe2a7a2..de56ae9479 100644 --- a/pallets/subtensor/src/utils/identity.rs +++ b/pallets/subtensor/src/utils/identity.rs @@ -25,7 +25,7 @@ impl Pallet { /// /// Returns `Ok(())` if the identity is successfully set, otherwise returns an error. pub fn do_set_identity( - origin: T::RuntimeOrigin, + origin: OriginFor, name: Vec, url: Vec, github_repo: Vec, @@ -96,7 +96,7 @@ impl Pallet { /// /// Returns `Ok(())` if the subnet identity is successfully set, otherwise returns an error. pub fn do_set_subnet_identity( - origin: T::RuntimeOrigin, + origin: OriginFor, netuid: NetUid, subnet_name: Vec, github_repo: Vec, diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 2481d56a6c..70278ebc1c 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -12,7 +12,7 @@ use subtensor_runtime_common::{AlphaCurrency, NetUid}; impl Pallet { pub fn ensure_subnet_owner_or_root( - o: T::RuntimeOrigin, + o: OriginFor, netuid: NetUid, ) -> Result<(), DispatchError> { let coldkey = ensure_signed_or_root(o); @@ -24,7 +24,7 @@ impl Pallet { } } - pub fn ensure_subnet_owner(o: T::RuntimeOrigin, netuid: NetUid) -> Result<(), DispatchError> { + pub fn ensure_subnet_owner(o: OriginFor, netuid: NetUid) -> Result<(), DispatchError> { let coldkey = ensure_signed(o); match coldkey { Ok(who) if SubnetOwner::::get(netuid) == who => Ok(()), @@ -653,7 +653,7 @@ impl Pallet { } pub fn do_set_senate_required_stake_perc( - origin: T::RuntimeOrigin, + origin: OriginFor, required_percent: u64, ) -> DispatchResult { ensure_root(origin)?; diff --git a/precompiles/src/extensions.rs b/precompiles/src/extensions.rs index ffa8da5778..41dbe19eb6 100644 --- a/precompiles/src/extensions.rs +++ b/precompiles/src/extensions.rs @@ -48,6 +48,20 @@ pub(crate) trait PrecompileHandleExt: PrecompileHandle { R::RuntimeCall: From, R::RuntimeCall: GetDispatchInfo + Dispatchable, R::RuntimeOrigin: From>, + { + Self::try_dispatch_runtime_call_with_custom_origin::(self, call, origin.into()) + } + + // Dispatches a runtime call, but also checks and records the gas costs. Uses custom origin. + fn try_dispatch_runtime_call_with_custom_origin( + &mut self, + call: Call, + origin: R::RuntimeOrigin, + ) -> EvmResult<()> + where + R: frame_system::Config + pallet_evm::Config, + R::RuntimeCall: From, + R::RuntimeCall: GetDispatchInfo + Dispatchable, { let call = R::RuntimeCall::from(call); let info = GetDispatchInfo::get_dispatch_info(&call); @@ -69,7 +83,7 @@ pub(crate) trait PrecompileHandleExt: PrecompileHandle { None, )?; - match call.dispatch(R::RuntimeOrigin::from(origin)) { + match call.dispatch(origin) { Ok(post_info) => { if post_info.pays_fee(&info) == Pays::Yes { let actual_weight = post_info.actual_weight.unwrap_or(info.call_weight); diff --git a/precompiles/src/staking.rs b/precompiles/src/staking.rs index 22756c2a9a..b4bf85e219 100644 --- a/precompiles/src/staking.rs +++ b/precompiles/src/staking.rs @@ -33,6 +33,7 @@ use pallet_evm::{ AddressMapping, BalanceConverter, EvmBalance, ExitError, PrecompileFailure, PrecompileHandle, SubstrateBalance, }; +use pallet_subtensor::EvmOriginHelper; use precompile_utils::EvmResult; use sp_core::{H256, U256}; use sp_runtime::traits::{Dispatchable, StaticLookup, UniqueSaturatedInto}; @@ -92,13 +93,15 @@ where let amount_staked = amount_rao.unique_saturated_into(); let hotkey = R::AccountId::from(address.0); let netuid = try_u16_from_u256(netuid)?; - let call = pallet_subtensor::Call::::add_stake { + let call = pallet_subtensor::Call::::add_stake_evm { hotkey, netuid: netuid.into(), amount_staked, }; - handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + let evm_origin = R::EvmOriginHelper::make_evm_origin(account_id); + + handle.try_dispatch_runtime_call_with_custom_origin::(call, evm_origin) } #[precompile::public("removeStake(bytes32,uint256,uint256)")] @@ -112,13 +115,15 @@ where let hotkey = R::AccountId::from(address.0); let netuid = try_u16_from_u256(netuid)?; let amount_unstaked: u64 = amount_alpha.unique_saturated_into(); - let call = pallet_subtensor::Call::::remove_stake { + let call = pallet_subtensor::Call::::remove_stake_evm { hotkey, netuid: netuid.into(), amount_unstaked: amount_unstaked.into(), }; - handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + let evm_origin = R::EvmOriginHelper::make_evm_origin(account_id); + + handle.try_dispatch_runtime_call_with_custom_origin::(call, evm_origin) } fn call_remove_stake_full_limit( @@ -130,13 +135,15 @@ where let account_id = handle.caller_account_id::(); let hotkey = R::AccountId::from(hotkey.0); let netuid = try_u16_from_u256(netuid)?; - let call = pallet_subtensor::Call::::remove_stake_full_limit { + let call = pallet_subtensor::Call::::remove_stake_full_limit_evm { hotkey, netuid: netuid.into(), limit_price, }; - handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + let evm_origin = R::EvmOriginHelper::make_evm_origin(account_id); + + handle.try_dispatch_runtime_call_with_custom_origin::(call, evm_origin) } #[precompile::public("removeStakeFull(bytes32,uint256)")] @@ -174,7 +181,7 @@ where 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::::move_stake { + let call = pallet_subtensor::Call::::move_stake_evm { origin_hotkey, destination_hotkey, origin_netuid: origin_netuid.into(), @@ -182,7 +189,9 @@ where alpha_amount: alpha_amount.into(), }; - handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + let evm_origin = R::EvmOriginHelper::make_evm_origin(account_id); + + handle.try_dispatch_runtime_call_with_custom_origin::(call, evm_origin) } #[precompile::public("transferStake(bytes32,bytes32,uint256,uint256,uint256)")] @@ -200,7 +209,7 @@ where 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 { + let call = pallet_subtensor::Call::::transfer_stake_evm { destination_coldkey, hotkey, origin_netuid: origin_netuid.into(), @@ -208,7 +217,9 @@ where alpha_amount: alpha_amount.into(), }; - handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + let evm_origin = R::EvmOriginHelper::make_evm_origin(account_id); + + handle.try_dispatch_runtime_call_with_custom_origin::(call, evm_origin) } #[precompile::public("getTotalColdkeyStake(bytes32)")] @@ -341,7 +352,7 @@ where let limit_price = limit_price_rao.unique_saturated_into(); let hotkey = R::AccountId::from(address.0); let netuid = try_u16_from_u256(netuid)?; - let call = pallet_subtensor::Call::::add_stake_limit { + let call = pallet_subtensor::Call::::add_stake_limit_evm { hotkey, netuid: netuid.into(), amount_staked, @@ -349,7 +360,9 @@ where allow_partial, }; - handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + let evm_origin = R::EvmOriginHelper::make_evm_origin(account_id); + + handle.try_dispatch_runtime_call_with_custom_origin::(call, evm_origin) } #[precompile::public("removeStakeLimit(bytes32,uint256,uint256,bool,uint256)")] @@ -366,15 +379,16 @@ where let netuid = try_u16_from_u256(netuid)?; let amount_unstaked: u64 = amount_alpha.unique_saturated_into(); let limit_price = limit_price_rao.unique_saturated_into(); - let call = pallet_subtensor::Call::::remove_stake_limit { + let call = pallet_subtensor::Call::::remove_stake_limit_evm { hotkey, netuid: netuid.into(), amount_unstaked: amount_unstaked.into(), limit_price, allow_partial, }; + let evm_origin = R::EvmOriginHelper::make_evm_origin(account_id); - handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + handle.try_dispatch_runtime_call_with_custom_origin::(call, evm_origin) } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 52b8d63e1a..a0ef3be264 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -104,6 +104,15 @@ use pallet_evm::{ Account as EVMAccount, BalanceConverter, EvmBalance, FeeCalculator, Runner, SubstrateBalance, }; +pub struct StakingJobsOnDrandPulse; +impl OnPulseReceived for StakingJobsOnDrandPulse { + fn on_pulse_received(_: RoundNumber) { + pallet_subtensor::pallet::Pallet::::process_staking_jobs( + frame_system::pallet::Pallet::::block_number(), + ); + } +} + // Drand impl pallet_drand::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -111,6 +120,8 @@ impl pallet_drand::Config for Runtime { type Verifier = pallet_drand::verifier::QuicknetVerifier; type UnsignedPriority = ConstU64<{ 1 << 20 }>; type HttpFetchTimeout = ConstU64<1_000>; + // Warning: ensure the weight is calculated correctly when changing this parameter. + type OnPulseReceived = StakingJobsOnDrandPulse; } impl frame_system::offchain::SigningTypes for Runtime { @@ -531,7 +542,7 @@ impl CanVote for CanVoteToTriumvirate { } } -use pallet_subtensor::{CollectiveInterface, MemberManagement, ProxyInterface}; +use pallet_subtensor::{CollectiveInterface, EvmOriginHelper, MemberManagement, ProxyInterface}; pub struct ManageSenateMembers; impl MemberManagement for ManageSenateMembers { fn add_member(account: &AccountId) -> DispatchResultWithPostInfo { @@ -1215,7 +1226,17 @@ parameter_types! { pub const LeaseDividendsDistributionInterval: BlockNumber = 100; // 100 blocks } +pub struct OriginHelper; + +impl EvmOriginHelper for OriginHelper { + fn make_evm_origin(account_id: AccountId) -> RuntimeOrigin { + RuntimeOrigin::from(pallet_subtensor::Origin::Evm { account_id }) + } +} + impl pallet_subtensor::Config for Runtime { + type EvmOriginHelper = OriginHelper; + type RuntimeOrigin = RuntimeOrigin; type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; type SudoRuntimeCall = RuntimeCall; @@ -1311,6 +1332,8 @@ impl pallet_subtensor_swap::Config for Runtime { type WeightInfo = pallet_subtensor_swap::weights::DefaultWeight; } +use pallet_drand::OnPulseReceived; +use pallet_drand::types::RoundNumber; use sp_runtime::BoundedVec; pub struct AuraPalletIntrf;