diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index e8796c9d4f..458a41655a 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -85,6 +85,24 @@ mod pallet_benchmarks { ); } + fn setup_subnet_sale_offer() + -> (NetUid, T::AccountId, T::AccountId, T::AccountId, TaoBalance) { + let netuid = NetUid::from(1); + let seller: T::AccountId = whitelisted_caller(); + let owner_hotkey: T::AccountId = account("owner_hotkey", 0, 0); + let authorized_buyer: T::AccountId = account("authorized_buyer", 0, 0); + let price = TaoBalance::from(1_000_000_000_u64); + + Subtensor::::init_new_network(netuid, 1); + SubnetOwner::::insert(netuid, seller.clone()); + assert_ok!(Subtensor::::set_subnet_owner_hotkey( + netuid, + &owner_hotkey + )); + + (netuid, seller, owner_hotkey, authorized_buyer, price) + } + #[benchmark] fn register() { let netuid = NetUid::from(1); @@ -1769,6 +1787,49 @@ mod pallet_benchmarks { assert!(!AccumulatedLeaseDividends::::contains_key(lease_id)); } + #[benchmark] + fn create_sale_offer() { + let (netuid, seller, owner_hotkey, authorized_buyer, price) = + setup_subnet_sale_offer::(); + + #[extrinsic_call] + _( + RawOrigin::Signed(seller.clone()), + netuid, + price, + Some(authorized_buyer.clone()), + ); + + let offer = SubnetSaleOffers::::get(netuid).unwrap(); + assert_eq!(offer.netuid, netuid); + assert_eq!(offer.seller, seller); + assert_eq!(offer.authorized_buyer, Some(authorized_buyer)); + assert_eq!(offer.price, price.into()); + assert_eq!(offer.created_at, frame_system::Pallet::::block_number()); + assert!(SubnetSaleFrozenColdkeys::::contains_key(&offer.seller)); + assert!(SubnetSaleFrozenHotkeys::::contains_key(&owner_hotkey)); + } + + #[benchmark] + fn cancel_sale_offer() { + let (netuid, seller, owner_hotkey, authorized_buyer, price) = + setup_subnet_sale_offer::(); + + assert_ok!(Subtensor::::create_sale_offer( + RawOrigin::Signed(seller.clone()).into(), + netuid, + price, + Some(authorized_buyer), + )); + + #[extrinsic_call] + _(RawOrigin::Signed(seller.clone()), netuid); + + assert!(!SubnetSaleOffers::::contains_key(netuid)); + assert!(!SubnetSaleFrozenColdkeys::::contains_key(seller)); + assert!(!SubnetSaleFrozenHotkeys::::contains_key(owner_hotkey)); + } + #[benchmark] fn update_symbol() { let coldkey: T::AccountId = whitelisted_caller(); diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index c61a71aa65..e38689ef72 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -304,7 +304,7 @@ impl Pallet { // --- 13. Token / mechanism / registration toggles. TokenSymbol::::remove(netuid); SubnetMechanism::::remove(netuid); - SubnetOwnerHotkey::::remove(netuid); + let owner_hotkey = SubnetOwnerHotkey::::take(netuid); NetworkRegistrationAllowed::::remove(netuid); NetworkPowRegistrationAllowed::::remove(netuid); @@ -466,6 +466,12 @@ impl Pallet { AccumulatedLeaseDividends::::remove(lease_id); } + // --- 23. Subnet sale offers: release sale locks. + if let Some(offer) = SubnetSaleOffers::::take(netuid) { + SubnetSaleFrozenColdkeys::::remove(&offer.seller); + SubnetSaleFrozenHotkeys::::remove(&owner_hotkey); + } + // --- 23: Locks cleanup Self::destroy_lock_maps(netuid); diff --git a/pallets/subtensor/src/guards/check_coldkey_swap.rs b/pallets/subtensor/src/guards/check_coldkey_swap.rs index 14b1a25ac9..22067bb0cf 100644 --- a/pallets/subtensor/src/guards/check_coldkey_swap.rs +++ b/pallets/subtensor/src/guards/check_coldkey_swap.rs @@ -83,7 +83,7 @@ where #[allow(clippy::expect_used, clippy::unwrap_used)] mod tests { use crate::{ColdkeySwapAnnouncements, ColdkeySwapDisputes, Error, tests::mock::*}; - use frame_support::{BoundedVec, assert_ok}; + use frame_support::{BoundedVec, assert_noop, assert_ok}; use frame_system::Call as SystemCall; use pallet_subtensor_proxy::Call as ProxyCall; use sp_core::U256; @@ -115,6 +115,11 @@ mod tests { RuntimeCall::SubtensorModule(crate::Call::register_network { hotkey: U256::from(1), }), + RuntimeCall::SubtensorModule(crate::Call::create_sale_offer { + netuid: 1u16.into(), + price: TaoBalance::from(1_000_u64), + authorized_buyer: None, + }), ] } @@ -189,9 +194,9 @@ mod tests { setup_swap_announced(&who); for call in forbidden_calls() { - assert_eq!( - call.dispatch(RuntimeOrigin::signed(who)).unwrap_err().error, - Error::::ColdkeySwapAnnounced.into() + assert_noop!( + call.dispatch(RuntimeOrigin::signed(who)), + Error::::ColdkeySwapAnnounced ); } }); @@ -228,9 +233,9 @@ mod tests { .collect::>(); for call in all_calls { - assert_eq!( - call.dispatch(RuntimeOrigin::signed(who)).unwrap_err().error, - Error::::ColdkeySwapDisputed.into() + assert_noop!( + call.dispatch(RuntimeOrigin::signed(who)), + Error::::ColdkeySwapDisputed ); } }); diff --git a/pallets/subtensor/src/guards/check_subnet_sale.rs b/pallets/subtensor/src/guards/check_subnet_sale.rs new file mode 100644 index 0000000000..742ddcba85 --- /dev/null +++ b/pallets/subtensor/src/guards/check_subnet_sale.rs @@ -0,0 +1,304 @@ +use crate::{Call, Config, Error, SubnetSaleFrozenColdkeys, SubnetSaleFrozenHotkeys}; +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, DispatchExtension, DispatchInfo, PostDispatchInfo}, + pallet_prelude::*, + traits::{IsSubType, OriginTrait}, +}; +use sp_runtime::traits::Dispatchable; +use sp_std::marker::PhantomData; + +type CallOf = ::RuntimeCall; +type DispatchableOriginOf = as Dispatchable>::RuntimeOrigin; + +/// Dispatch extension that blocks seller coldkey and owner hotkey calls during a subnet sale. +/// +/// When a subnet sale offer is active: +/// - The frozen seller coldkey can only cancel the sale offer or submit MEV-protected calls. +/// - The frozen owner hotkey can only submit MEV-protected calls. +/// +/// Root origin bypasses this extension entirely. +/// Non-signed origins pass through. +/// +/// Because this is a `DispatchExtension` (not a `TransactionExtension`), it fires at every +/// `call.dispatch(origin)` site, including inside proxy dispatch with the resolved origin. +pub struct CheckSubnetSale(PhantomData); + +impl DispatchExtension<::RuntimeCall> for CheckSubnetSale +where + T: Config + pallet_shield::Config, + ::RuntimeCall: Dispatchable + + IsSubType> + + IsSubType>, + DispatchableOriginOf: OriginTrait, +{ + type Pre = (); + + fn weight(_call: &CallOf) -> Weight { + T::DbWeight::get().reads(2) + } + + fn pre_dispatch( + origin: &DispatchableOriginOf, + call: &CallOf, + ) -> Result { + let Some(who) = origin.as_signer() else { + return Ok(()); + }; + + let is_mev_protected = matches!( + IsSubType::>::is_sub_type(call), + Some(pallet_shield::Call::submit_encrypted { .. }) + ); + let is_sale_frozen_coldkey = SubnetSaleFrozenColdkeys::::contains_key(who); + let is_sale_frozen_owner_hotkey = SubnetSaleFrozenHotkeys::::contains_key(who); + let is_sale_cancel = matches!(call.is_sub_type(), Some(Call::cancel_sale_offer { .. })); + + if is_sale_frozen_coldkey && !is_sale_cancel && !is_mev_protected { + return Err(Error::::ColdkeyLockedDuringSale.into()); + } + + if is_sale_frozen_owner_hotkey && !is_mev_protected { + return Err(Error::::HotkeyLockedDuringSale.into()); + } + + Ok(()) + } +} + +#[cfg(test)] +#[allow(clippy::expect_used, clippy::unwrap_used)] +mod tests { + use crate::{Error, SubnetSaleFrozenColdkeys, SubnetSaleFrozenHotkeys, tests::mock::*}; + use frame_support::{ + BoundedVec, assert_noop, assert_ok, + dispatch::{DispatchErrorWithPostInfo, DispatchExtension}, + }; + use frame_system::Call as SystemCall; + use pallet_subtensor_proxy::Call as ProxyCall; + use sp_core::U256; + use sp_runtime::traits::Dispatchable; + use subtensor_runtime_common::{NetUid, ProxyType, TaoBalance}; + + type SaleGuard = super::CheckSubnetSale; + + fn pre_dispatch( + origin: RuntimeOrigin, + call: &RuntimeCall, + ) -> Result<(), DispatchErrorWithPostInfo> { + >::pre_dispatch(&origin, call) + } + + fn sale_netuid() -> NetUid { + NetUid::from(1) + } + + fn freeze_coldkey(who: U256) { + SubnetSaleFrozenColdkeys::::insert(who, ()); + } + + fn freeze_owner_hotkey(who: U256) { + SubnetSaleFrozenHotkeys::::insert(who, ()); + } + + fn remark_call() -> RuntimeCall { + RuntimeCall::System(SystemCall::remark { remark: vec![] }) + } + + fn cancel_call() -> RuntimeCall { + RuntimeCall::SubtensorModule(crate::Call::cancel_sale_offer { + netuid: sale_netuid(), + }) + } + + fn shielded_call() -> RuntimeCall { + RuntimeCall::Shield(pallet_shield::Call::submit_encrypted { + ciphertext: BoundedVec::truncate_from(vec![1, 2, 3, 4]), + }) + } + + fn add_balance_to_coldkey_account(coldkey: &U256, tao: TaoBalance) { + let credit = SubtensorModule::mint_tao(tao); + let _ = SubtensorModule::spend_tao(coldkey, credit, tao).unwrap(); + } + + #[test] + fn no_sale_freeze_allows_signed_calls() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + + assert_ok!(pre_dispatch(RuntimeOrigin::signed(who), &remark_call())); + }); + } + + #[test] + fn none_and_root_bypass_sale_freezes() { + new_test_ext(1).execute_with(|| { + let who = U256::from(1); + freeze_coldkey(who); + freeze_owner_hotkey(who); + + assert_ok!(pre_dispatch(RuntimeOrigin::none(), &remark_call())); + assert_ok!(pre_dispatch(RuntimeOrigin::root(), &remark_call())); + }); + } + + #[test] + fn freeze_coldkey_blocks_regular_signed_calls() { + new_test_ext(1).execute_with(|| { + let seller = U256::from(1); + freeze_coldkey(seller); + + assert_noop!( + pre_dispatch(RuntimeOrigin::signed(seller), &remark_call()), + Error::::ColdkeyLockedDuringSale + ); + }); + } + + #[test] + fn freeze_owner_hotkey_blocks_regular_signed_calls() { + new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(2); + freeze_owner_hotkey(owner_hotkey); + + assert_noop!( + pre_dispatch(RuntimeOrigin::signed(owner_hotkey), &remark_call()), + Error::::HotkeyLockedDuringSale + ); + }); + } + + #[test] + fn freeze_coldkey_allows_sale_cancellation() { + new_test_ext(1).execute_with(|| { + let seller = U256::from(1); + freeze_coldkey(seller); + + assert_ok!(pre_dispatch(RuntimeOrigin::signed(seller), &cancel_call())); + }); + } + + #[test] + fn freeze_owner_hotkey_does_not_allow_sale_cancellation() { + new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(2); + freeze_owner_hotkey(owner_hotkey); + + assert_noop!( + pre_dispatch(RuntimeOrigin::signed(owner_hotkey), &cancel_call()), + Error::::HotkeyLockedDuringSale + ); + }); + } + + #[test] + fn frozen_owner_hotkey_rejects_sale_cancellation_even_if_coldkey() { + new_test_ext(1).execute_with(|| { + let seller_and_owner_hotkey = U256::from(1); + freeze_coldkey(seller_and_owner_hotkey); + freeze_owner_hotkey(seller_and_owner_hotkey); + + assert_noop!( + pre_dispatch( + RuntimeOrigin::signed(seller_and_owner_hotkey), + &cancel_call() + ), + Error::::HotkeyLockedDuringSale + ); + }); + } + + #[test] + fn mev_protected_calls_are_allowed_for_sale_frozen_accounts() { + new_test_ext(1).execute_with(|| { + let seller = U256::from(1); + let owner_hotkey = U256::from(2); + freeze_coldkey(seller); + freeze_owner_hotkey(owner_hotkey); + + assert_ok!(pre_dispatch( + RuntimeOrigin::signed(seller), + &shielded_call() + )); + assert_ok!(pre_dispatch( + RuntimeOrigin::signed(owner_hotkey), + &shielded_call() + )); + }); + } + + #[test] + fn proxied_call_from_sale_frozen_coldkey_is_blocked() { + new_test_ext(1).execute_with(|| { + let real = U256::from(1); + let delegate = U256::from(2); + freeze_coldkey(real); + + add_balance_to_coldkey_account(&real, 1_000_000_000.into()); + add_balance_to_coldkey_account(&delegate, 1_000_000_000.into()); + + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(real), + delegate, + ProxyType::Any, + 0 + )); + + let proxy_call = RuntimeCall::Proxy(ProxyCall::proxy { + real, + force_proxy_type: None, + call: Box::new(remark_call()), + }); + + assert_ok!(proxy_call.dispatch(RuntimeOrigin::signed(delegate))); + assert_eq!( + pallet_subtensor_proxy::LastCallResult::::get(real), + Some(Err(Error::::ColdkeyLockedDuringSale.into())) + ); + }); + } + + #[test] + fn nested_proxied_call_from_sale_frozen_owner_hotkey_is_blocked() { + new_test_ext(1).execute_with(|| { + let real = U256::from(1); + let delegate1 = U256::from(2); + let delegate2 = U256::from(3); + freeze_owner_hotkey(real); + + add_balance_to_coldkey_account(&real, 1_000_000_000.into()); + add_balance_to_coldkey_account(&delegate1, 1_000_000_000.into()); + add_balance_to_coldkey_account(&delegate2, 1_000_000_000.into()); + + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(real), + delegate1, + ProxyType::Any, + 0 + )); + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(delegate1), + delegate2, + ProxyType::Any, + 0 + )); + + let inner_proxy = RuntimeCall::Proxy(ProxyCall::proxy { + real, + force_proxy_type: None, + call: Box::new(remark_call()), + }); + let outer_proxy = RuntimeCall::Proxy(ProxyCall::proxy { + real: delegate1, + force_proxy_type: None, + call: Box::new(inner_proxy), + }); + + assert_ok!(outer_proxy.dispatch(RuntimeOrigin::signed(delegate2))); + assert_eq!( + pallet_subtensor_proxy::LastCallResult::::get(real), + Some(Err(Error::::HotkeyLockedDuringSale.into())) + ); + }); + } +} diff --git a/pallets/subtensor/src/guards/mod.rs b/pallets/subtensor/src/guards/mod.rs index 44fba0c50f..ba46739007 100644 --- a/pallets/subtensor/src/guards/mod.rs +++ b/pallets/subtensor/src/guards/mod.rs @@ -1,3 +1,5 @@ mod check_coldkey_swap; +mod check_subnet_sale; pub use check_coldkey_swap::*; +pub use check_subnet_sale::*; diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 7ce25b65b6..f51ff0e015 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -83,7 +83,10 @@ pub mod pallet { use crate::RateLimitKey; use crate::migrations; use crate::staking::lock::LockState; - use crate::subnets::leasing::{LeaseId, SubnetLeaseOf}; + use crate::subnets::{ + leasing::{LeaseId, SubnetLeaseOf}, + sale_offer::SubnetSaleOfferOf, + }; use frame_support::Twox64Concat; use frame_support::{ BoundedVec, @@ -2544,6 +2547,24 @@ pub mod pallet { pub type AccumulatedLeaseDividends = StorageMap<_, Twox64Concat, LeaseId, AlphaBalance, ValueQuery, DefaultZeroAlpha>; + /// =========================== + /// ==== Subnet Sale Offers ==== + /// =========================== + /// --- MAP ( netuid ) --> subnet sale offer | Active sale offer for a subnet. + #[pallet::storage] + pub type SubnetSaleOffers = + StorageMap<_, Twox64Concat, NetUid, SubnetSaleOfferOf, OptionQuery>; + + /// --- MAP ( coldkey ) --> () | Owner coldkeys frozen by an active subnet sale offer. + #[pallet::storage] + pub type SubnetSaleFrozenColdkeys = + StorageMap<_, Identity, T::AccountId, (), OptionQuery>; + + /// --- MAP ( hotkey ) --> () | Owner hotkeys frozen by an active subnet sale offer. + #[pallet::storage] + pub type SubnetSaleFrozenHotkeys = + StorageMap<_, Identity, T::AccountId, (), OptionQuery>; + /// --- ITEM ( CommitRevealWeightsVersion ) #[pallet::storage] pub type CommitRevealWeightsVersion = diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index b471328aec..db11427752 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2593,5 +2593,31 @@ mod dispatches { let coldkey = ensure_signed(origin)?; Self::do_set_perpetual_lock(&coldkey, netuid, enabled) } + + /// Create a sale offer for an owned subnet. + /// + /// While the offer exists, the seller coldkey, subnet, and owner hotkey are locked. + /// The seller can optionally restrict the future buyer to a specific coldkey. + #[pallet::call_index(139)] + #[pallet::weight(::WeightInfo::create_sale_offer())] + pub fn create_sale_offer( + origin: OriginFor, + netuid: NetUid, + price: TaoBalance, + authorized_buyer: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_create_sale_offer(who, netuid, price, authorized_buyer) + } + + /// Cancel an active subnet sale offer. + /// + /// The seller that created the offer or root can cancel it. + #[pallet::call_index(140)] + #[pallet::weight(::WeightInfo::cancel_sale_offer())] + pub fn cancel_sale_offer(origin: OriginFor, netuid: NetUid) -> DispatchResult { + let maybe_who = crate::system::ensure_signed_or_root(origin)?; + Self::do_cancel_sale_offer(maybe_who, netuid) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 46343b6ed1..d4d3d8e429 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -229,6 +229,14 @@ mod errors { LeaseHasNoEndBlock, /// Lease has not ended. LeaseHasNotEnded, + /// Active subnet sale offer already exists for this subnet. + SaleOfferAlreadyExists, + /// No active subnet sale offer exists for this subnet. + SaleOfferNotFound, + /// The seller coldkey is locked by an active subnet sale offer. + ColdkeyLockedDuringSale, + /// The subnet owner hotkey is locked by an active subnet sale offer. + HotkeyLockedDuringSale, /// An overflow occurred. Overflow, /// Beneficiary does not own hotkey. diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 918baf1107..b8f20aee5e 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -394,6 +394,26 @@ mod events { netuid: NetUid, }, + /// A subnet sale offer has been created and the seller coldkey has been locked. + SubnetSaleOfferCreated { + /// The subnet owner selling the subnet. + seller: T::AccountId, + /// The subnet ID. + netuid: NetUid, + /// The sale price. + price: TaoBalance, + /// Optional buyer coldkey authorized to consume the offer. + authorized_buyer: Option, + }, + + /// A subnet sale offer has been cancelled and its locks have been released. + SubnetSaleOfferCancelled { + /// The subnet owner that cancelled the offer. + seller: T::AccountId, + /// The subnet ID. + netuid: NetUid, + }, + /// The symbol for a subnet has been updated. SymbolUpdated { /// The subnet ID diff --git a/pallets/subtensor/src/subnets/mod.rs b/pallets/subtensor/src/subnets/mod.rs index e93628eef4..e8ed889456 100644 --- a/pallets/subtensor/src/subnets/mod.rs +++ b/pallets/subtensor/src/subnets/mod.rs @@ -2,6 +2,7 @@ use super::*; pub mod leasing; pub mod mechanism; pub mod registration; +pub mod sale_offer; pub mod serving; pub mod subnet; pub mod symbols; diff --git a/pallets/subtensor/src/subnets/sale_offer.rs b/pallets/subtensor/src/subnets/sale_offer.rs new file mode 100644 index 0000000000..5ba1cbd516 --- /dev/null +++ b/pallets/subtensor/src/subnets/sale_offer.rs @@ -0,0 +1,107 @@ +//! Subnet sale offers and sale-time freezes. +//! +//! This module intentionally only owns the seller-side primitive: listing a subnet +//! for sale freezes the seller coldkey, subnet, and owner hotkey until the offer is +//! cancelled or later consumed by a sale finalization path. + +use super::*; +use frame_support::traits::fungible; +use frame_system::pallet_prelude::BlockNumberFor; +use subtensor_runtime_common::{NetUid, TaoBalance}; + +pub type CurrencyOf = ::Currency; + +pub type BalanceOf = + as fungible::Inspect<::AccountId>>::Balance; + +#[freeze_struct("801dda6b57266829")] +#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct SubnetSaleOffer { + /// The subnet being sold. + pub netuid: NetUid, + /// The subnet owner coldkey that created the offer. + pub seller: AccountId, + /// Optional coldkey that is allowed to consume this offer. + pub authorized_buyer: Option, + /// Sale price expected by the seller. + pub price: Balance, + /// Block at which the sale offer was created. + pub created_at: BlockNumber, +} + +pub type SubnetSaleOfferOf = SubnetSaleOffer, BalanceOf, BlockNumberFor>; + +impl Pallet { + pub fn do_create_sale_offer( + seller: T::AccountId, + netuid: NetUid, + price: TaoBalance, + authorized_buyer: Option, + ) -> DispatchResult { + ensure!(price > TaoBalance::from(0_u64), Error::::AmountTooLow); + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + ensure!( + SubnetOwner::::get(netuid) == seller, + Error::::NotSubnetOwner + ); + ensure!( + !SubnetSaleOffers::::contains_key(netuid), + Error::::SaleOfferAlreadyExists + ); + ensure!( + !SubnetSaleFrozenColdkeys::::contains_key(&seller), + Error::::ColdkeyLockedDuringSale + ); + let owner_hotkey = SubnetOwnerHotkey::::try_get(netuid) + .map_err(|_| Error::::HotKeyAccountNotExists)?; + ensure!( + !SubnetSaleFrozenHotkeys::::contains_key(&owner_hotkey), + Error::::HotkeyLockedDuringSale + ); + + SubnetSaleOffers::::insert( + netuid, + SubnetSaleOffer { + netuid, + seller: seller.clone(), + authorized_buyer: authorized_buyer.clone(), + price: price.into(), + created_at: frame_system::Pallet::::block_number(), + }, + ); + SubnetSaleFrozenColdkeys::::insert(&seller, ()); + SubnetSaleFrozenHotkeys::::insert(&owner_hotkey, ()); + + Self::deposit_event(Event::SubnetSaleOfferCreated { + seller, + netuid, + price, + authorized_buyer, + }); + + Ok(()) + } + + pub fn do_cancel_sale_offer( + maybe_seller: Option, + netuid: NetUid, + ) -> DispatchResult { + let offer = SubnetSaleOffers::::get(netuid).ok_or(Error::::SaleOfferNotFound)?; + + // If the caller is not the seller, they are root. + if let Some(seller) = maybe_seller { + ensure!(seller == offer.seller, Error::::NotSubnetOwner); + } + + let seller = offer.seller.clone(); + SubnetSaleOffers::::remove(offer.netuid); + SubnetSaleFrozenColdkeys::::remove(&offer.seller); + if let Ok(owner_hotkey) = SubnetOwnerHotkey::::try_get(offer.netuid) { + SubnetSaleFrozenHotkeys::::remove(owner_hotkey); + } + + Self::deposit_event(Event::SubnetSaleOfferCancelled { seller, netuid }); + + Ok(()) + } +} diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 426c2cd1bd..a9c7a0c987 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -84,6 +84,46 @@ pub type Balance = TaoBalance; #[allow(dead_code)] pub type BlockNumber = u64; +const DEFAULT_EXISTENTIAL_DEPOSIT: Balance = TaoBalance::new(1); + +std::thread_local! { + static TEST_EXISTENTIAL_DEPOSIT: RefCell = + const { RefCell::new(DEFAULT_EXISTENTIAL_DEPOSIT) }; +} + +pub struct ExistentialDeposit; + +impl ExistentialDeposit { + pub fn get() -> Balance { + >::get() + } +} + +impl Get for ExistentialDeposit { + fn get() -> Balance { + TEST_EXISTENTIAL_DEPOSIT.with(|ed| *ed.borrow()) + } +} + +pub struct TestExternalitiesWithExistentialDeposit { + previous_existential_deposit: Balance, + ext: sp_io::TestExternalities, +} + +impl TestExternalitiesWithExistentialDeposit { + pub fn execute_with(mut self, execute: impl FnOnce() -> R) -> R { + self.ext.execute_with(execute) + } +} + +impl Drop for TestExternalitiesWithExistentialDeposit { + fn drop(&mut self) { + TEST_EXISTENTIAL_DEPOSIT.with(|ed| { + ed.replace(self.previous_existential_deposit); + }); + } +} + #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Test { type Balance = Balance; @@ -157,7 +197,7 @@ impl system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; type Nonce = u64; type Block = Block; - type DispatchExtension = crate::CheckColdkeySwap; + type DispatchExtension = (crate::CheckColdkeySwap, crate::CheckSubnetSale); } parameter_types! { @@ -182,7 +222,6 @@ parameter_types! { Weight::from_parts(2_000_000_000_000, u64::MAX), Perbill::from_percent(75), ); - pub const ExistentialDeposit: Balance = TaoBalance::new(1); pub const TransactionByteFee: Balance = TaoBalance::new(100); pub const SDebug:u64 = 1; pub const InitialRho: u16 = 30; @@ -614,6 +653,20 @@ pub fn new_test_ext(block_number: BlockNumber) -> sp_io::TestExternalities { ext } +pub fn test_ext_with_existential_deposit( + block_number: BlockNumber, + existential_deposit: Balance, +) -> TestExternalitiesWithExistentialDeposit { + let previous_existential_deposit = + TEST_EXISTENTIAL_DEPOSIT.with(|ed| ed.replace(existential_deposit)); + let ext = new_test_ext(block_number); + + TestExternalitiesWithExistentialDeposit { + previous_existential_deposit, + ext, + } +} + #[allow(dead_code)] pub fn test_ext_with_balances(balances: Vec<(U256, u128)>) -> sp_io::TestExternalities { init_logs_for_tests(); diff --git a/pallets/subtensor/src/tests/mock_high_ed.rs b/pallets/subtensor/src/tests/mock_high_ed.rs deleted file mode 100644 index b1eb2a354c..0000000000 --- a/pallets/subtensor/src/tests/mock_high_ed.rs +++ /dev/null @@ -1,562 +0,0 @@ -#![allow( - clippy::arithmetic_side_effects, - clippy::expect_used, - clippy::unwrap_used -)] - -use core::num::NonZeroU64; - -use crate::*; -use frame_support::traits::{Everything, InstanceFilter}; -use frame_support::weights::Weight; -use frame_support::weights::constants::RocksDbWeight; -use frame_support::{PalletId, derive_impl}; -use frame_support::{parameter_types, traits::PrivilegeCmp}; -use frame_system as system; -use frame_system::{EnsureRoot, limits}; -use pallet_subtensor_proxy as pallet_proxy; -use sp_core::{ConstU64, H256, U256, offchain::KeyTypeId}; -use sp_runtime::Perbill; -use sp_runtime::{ - BuildStorage, Percent, - traits::{BlakeTwo256, IdentityLookup}, -}; -use sp_std::{cmp::Ordering, sync::OnceLock}; -use sp_tracing::tracing_subscriber; -use substrate_fixed::types::U64F64; -use subtensor_runtime_common::{AuthorshipInfo, NetUid, TaoBalance}; -use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; -type Block = frame_system::mocking::MockBlock; - -// Configure a mock runtime to test the pallet. -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system = 1, - Balances: pallet_balances = 2, - Shield: pallet_shield = 3, - SubtensorModule: crate = 4, - AlphaAssets: pallet_alpha_assets = 5, - Scheduler: pallet_scheduler = 6, - Preimage: pallet_preimage = 7, - Drand: pallet_drand = 8, - Swap: pallet_subtensor_swap = 9, - Crowdloan: pallet_crowdloan = 10, - Proxy: pallet_subtensor_proxy = 11, - } -); - -#[allow(dead_code)] -pub type TestRuntimeCall = frame_system::Call; - -pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); - -#[allow(dead_code)] -pub type AccountId = U256; - -// The address format for describing accounts. -#[allow(dead_code)] -pub type Address = AccountId; - -// Balance of an account. -#[allow(dead_code)] -pub type Balance = TaoBalance; - -// An index to a block. -#[allow(dead_code)] -pub type BlockNumber = u64; - -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for Test { - type Balance = Balance; - type RuntimeEvent = RuntimeEvent; - type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type MaxLocks = (); - type WeightInfo = (); - type MaxReserves = (); - type ReserveIdentifier = (); - type RuntimeHoldReason = (); - type FreezeIdentifier = (); - type MaxFreezes = (); -} - -impl pallet_shield::Config for Test { - type AuthorityId = sp_core::sr25519::Public; - type FindAuthors = (); - type RuntimeCall = RuntimeCall; - type ExtrinsicDecryptor = (); - type WeightInfo = (); -} - -impl pallet_alpha_assets::Config for Test {} - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = U256; - type Lookup = IdentityLookup; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - type Nonce = u64; - type Block = Block; - type DispatchExtension = crate::CheckColdkeySwap; -} - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; -} - -pub const MOCK_BLOCK_BUILDER: u64 = 12345u64; - -pub struct MockAuthorshipProvider; - -impl AuthorshipInfo for MockAuthorshipProvider { - fn author() -> Option { - Some(U256::from(MOCK_BLOCK_BUILDER)) - } -} - -parameter_types! { - pub const InitialMinAllowedWeights: u16 = 0; - pub const InitialEmissionValue: u16 = 0; - pub BlockWeights: limits::BlockWeights = limits::BlockWeights::with_sensible_defaults( - Weight::from_parts(2_000_000_000_000, u64::MAX), - Perbill::from_percent(75), - ); - pub const ExistentialDeposit: Balance = TaoBalance::new(100); - pub const TransactionByteFee: Balance = TaoBalance::new(100); - pub const SDebug:u64 = 1; - pub const InitialRho: u16 = 30; - pub const InitialAlphaSigmoidSteepness: i16 = 1000; - pub const InitialKappa: u16 = 32_767; - pub const InitialTempo: u16 = 360; - pub const SelfOwnership: u64 = 2; - pub const InitialImmunityPeriod: u16 = 2; - pub const InitialMinAllowedUids: u16 = 2; - pub const InitialMaxAllowedUids: u16 = 256; - pub const InitialBondsMovingAverage: u64 = 900_000; - pub const InitialBondsPenalty:u16 = u16::MAX; - pub const InitialBondsResetOn: bool = false; - pub const InitialStakePruningMin: u16 = 0; - pub const InitialFoundationDistribution: u64 = 0; - pub const InitialDefaultDelegateTake: u16 = 11_796; // 18%, same as in production - pub const InitialMinDelegateTake: u16 = 5_898; // 9%; - pub const InitialDefaultChildKeyTake: u16 = 0 ;// 0 % - pub const InitialMinChildKeyTake: u16 = 0; // 0 %; - pub const InitialMaxChildKeyTake: u16 = 11_796; // 18 %; - pub const InitialWeightsVersionKey: u16 = 0; - pub const InitialServingRateLimit: u64 = 0; // No limit. - pub const InitialTxRateLimit: u64 = 0; // Disable rate limit for testing - pub const InitialTxDelegateTakeRateLimit: u64 = 1; // 1 block take rate limit for testing - pub const InitialTxChildKeyTakeRateLimit: u64 = 1; // 1 block take rate limit for testing - pub const InitialBurn: u64 = 0; - pub const InitialMinBurn: u64 = 500_000; - pub const InitialMinStake: u64 = 2_000_000; - pub const InitialMaxBurn: u64 = 1_000_000_000; - pub const MinBurnUpperBound: TaoBalance = TaoBalance::new(1_000_000_000); // 1 TAO - pub const MaxBurnLowerBound: TaoBalance = TaoBalance::new(100_000_000); // 0.1 TAO - pub const InitialValidatorPruneLen: u64 = 0; - pub const InitialScalingLawPower: u16 = 50; - pub const InitialMaxAllowedValidators: u16 = 100; - pub const InitialIssuance: u64 = 0; - pub const InitialDifficulty: u64 = 10000; - pub const InitialActivityCutoff: u16 = 5000; - pub const InitialAdjustmentInterval: u16 = 100; - pub const InitialAdjustmentAlpha: u64 = 0; // no weight to previous value. - pub const InitialMaxRegistrationsPerBlock: u16 = 3; - pub const InitialTargetRegistrationsPerInterval: u16 = 2; - pub const InitialPruningScore : u16 = u16::MAX; - pub const InitialRegistrationRequirement: u16 = u16::MAX; // Top 100% - pub const InitialMinDifficulty: u64 = 1; - pub const InitialMaxDifficulty: u64 = u64::MAX; - pub const InitialRAORecycledForRegistration: u64 = 0; - pub const InitialNetworkImmunityPeriod: u64 = 1_296_000; - pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; - pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. - pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. - pub const InitialNetworkRateLimit: u64 = 0; - pub const InitialKeySwapCost: u64 = 1_000_000_000; - pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default - pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default - pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn - pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapAnnouncementDelay: u64 = 50; - pub const InitialColdkeySwapReannouncementDelay: u64 = 10; - pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days - pub const InitialTaoWeight: u64 = 0; // 100% global weight. - pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks - pub const InitialStartCallDelay: u64 = 0; // 0 days - pub const InitialKeySwapOnSubnetCost: u64 = 10_000_000; - pub const HotkeySwapOnSubnetInterval: u64 = 15; // 15 block, should be bigger than subnet number, then trigger clean up for all subnets - pub const MaxContributorsPerLeaseToRemove: u32 = 3; - pub const LeaseDividendsDistributionInterval: u32 = 100; - pub const MaxImmuneUidsPercentage: Percent = Percent::from_percent(80); - pub const EvmKeyAssociateRateLimit: u64 = 10; - pub const SubtensorPalletId: PalletId = PalletId(*b"subtensr"); - pub const BurnAccountId: PalletId = PalletId(*b"burntnsr"); -} - -impl crate::Config for Test { - type RuntimeCall = RuntimeCall; - type Currency = Balances; - type InitialIssuance = InitialIssuance; - type SudoRuntimeCall = TestRuntimeCall; - type Scheduler = Scheduler; - type InitialMinAllowedWeights = InitialMinAllowedWeights; - type InitialEmissionValue = InitialEmissionValue; - type InitialTempo = InitialTempo; - type InitialDifficulty = InitialDifficulty; - type InitialAdjustmentInterval = InitialAdjustmentInterval; - type InitialAdjustmentAlpha = InitialAdjustmentAlpha; - type InitialTargetRegistrationsPerInterval = InitialTargetRegistrationsPerInterval; - type InitialRho = InitialRho; - type InitialAlphaSigmoidSteepness = InitialAlphaSigmoidSteepness; - type InitialKappa = InitialKappa; - type InitialMinAllowedUids = InitialMinAllowedUids; - type InitialMaxAllowedUids = InitialMaxAllowedUids; - type InitialValidatorPruneLen = InitialValidatorPruneLen; - type InitialScalingLawPower = InitialScalingLawPower; - type InitialImmunityPeriod = InitialImmunityPeriod; - type InitialActivityCutoff = InitialActivityCutoff; - type InitialMaxRegistrationsPerBlock = InitialMaxRegistrationsPerBlock; - type InitialPruningScore = InitialPruningScore; - type InitialBondsMovingAverage = InitialBondsMovingAverage; - type InitialBondsPenalty = InitialBondsPenalty; - type InitialBondsResetOn = InitialBondsResetOn; - type InitialMaxAllowedValidators = InitialMaxAllowedValidators; - type InitialDefaultDelegateTake = InitialDefaultDelegateTake; - type InitialMinDelegateTake = InitialMinDelegateTake; - type InitialDefaultChildKeyTake = InitialDefaultChildKeyTake; - type InitialMinChildKeyTake = InitialMinChildKeyTake; - type InitialMaxChildKeyTake = InitialMaxChildKeyTake; - type InitialTxChildKeyTakeRateLimit = InitialTxChildKeyTakeRateLimit; - type InitialWeightsVersionKey = InitialWeightsVersionKey; - type InitialMaxDifficulty = InitialMaxDifficulty; - type InitialMinDifficulty = InitialMinDifficulty; - type InitialServingRateLimit = InitialServingRateLimit; - type InitialTxRateLimit = InitialTxRateLimit; - type InitialTxDelegateTakeRateLimit = InitialTxDelegateTakeRateLimit; - type InitialBurn = InitialBurn; - type InitialMaxBurn = InitialMaxBurn; - type InitialMinBurn = InitialMinBurn; - type InitialMinStake = InitialMinStake; - type MinBurnUpperBound = MinBurnUpperBound; - type MaxBurnLowerBound = MaxBurnLowerBound; - type InitialRAORecycledForRegistration = InitialRAORecycledForRegistration; - type InitialNetworkImmunityPeriod = InitialNetworkImmunityPeriod; - type InitialNetworkMinLockCost = InitialNetworkMinLockCost; - type InitialSubnetOwnerCut = InitialSubnetOwnerCut; - type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval; - type InitialNetworkRateLimit = InitialNetworkRateLimit; - type KeySwapCost = InitialKeySwapCost; - type AlphaHigh = InitialAlphaHigh; - type AlphaLow = InitialAlphaLow; - type LiquidAlphaOn = InitialLiquidAlphaOn; - type Yuma3On = InitialYuma3On; - type Preimages = Preimage; - type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; - type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; - type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; - type InitialTaoWeight = InitialTaoWeight; - type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; - type InitialStartCallDelay = InitialStartCallDelay; - type SwapInterface = pallet_subtensor_swap::Pallet; - type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost; - type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; - type ProxyInterface = (); - type LeaseDividendsDistributionInterval = LeaseDividendsDistributionInterval; - type GetCommitments = (); - type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; - type CommitmentsInterface = CommitmentsI; - type AlphaAssets = AlphaAssets; - type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit; - type AuthorshipProvider = MockAuthorshipProvider; - type SubtensorPalletId = SubtensorPalletId; - type BurnAccountId = BurnAccountId; - type WeightInfo = (); -} - -// Swap-related parameter types -parameter_types! { - pub const SwapProtocolId: PalletId = PalletId(*b"ten/swap"); - pub const SwapMaxFeeRate: u16 = 10000; // 15.26% - pub const SwapMinimumLiquidity: u64 = 1_000; - pub const SwapMinimumReserve: NonZeroU64 = NonZeroU64::new(100).unwrap(); -} - -impl pallet_subtensor_swap::Config for Test { - type SubnetInfo = SubtensorModule; - type BalanceOps = SubtensorModule; - type ProtocolId = SwapProtocolId; - type TaoReserve = TaoBalanceReserve; - type AlphaReserve = AlphaBalanceReserve; - type MaxFeeRate = SwapMaxFeeRate; - type MinimumLiquidity = SwapMinimumLiquidity; - type MinimumReserve = SwapMinimumReserve; - type WeightInfo = (); - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = (); -} - -pub struct OriginPrivilegeCmp; - -impl PrivilegeCmp for OriginPrivilegeCmp { - fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option { - Some(Ordering::Less) - } -} - -pub struct CommitmentsI; -impl CommitmentsInterface for CommitmentsI { - fn purge_netuid(_netuid: NetUid) {} -} - -parameter_types! { - pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * - BlockWeights::get().max_block; - pub const MaxScheduledPerBlock: u32 = 50; - pub const NoPreimagePostponement: Option = Some(10); -} - -impl pallet_scheduler::Config for Test { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeEvent = RuntimeEvent; - type PalletsOrigin = OriginCaller; - type RuntimeCall = RuntimeCall; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureRoot; - type MaxScheduledPerBlock = MaxScheduledPerBlock; - type WeightInfo = pallet_scheduler::weights::SubstrateWeight; - type OriginPrivilegeCmp = OriginPrivilegeCmp; - type Preimages = Preimage; - type BlockNumberProvider = System; -} - -parameter_types! { - pub const PreimageMaxSize: u32 = 4096 * 1024; - pub const PreimageBaseDeposit: Balance = TaoBalance::new(1); - pub const PreimageByteDeposit: Balance = TaoBalance::new(1); -} - -impl pallet_preimage::Config for Test { - type WeightInfo = pallet_preimage::weights::SubstrateWeight; - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type ManagerOrigin = EnsureRoot; - type Consideration = (); -} - -parameter_types! { - pub const CrowdloanPalletId: PalletId = PalletId(*b"bt/cloan"); - pub const MinimumDeposit: u64 = 50; - pub const AbsoluteMinimumContribution: u64 = 10; - pub const MinimumBlockDuration: u64 = 20; - pub const MaximumBlockDuration: u64 = 100; - pub const RefundContributorsLimit: u32 = 5; - pub const MaxContributors: u32 = 10; -} - -impl pallet_crowdloan::Config for Test { - type PalletId = CrowdloanPalletId; - type Currency = Balances; - type RuntimeCall = RuntimeCall; - type WeightInfo = pallet_crowdloan::weights::SubstrateWeight; - type Preimages = Preimage; - type MinimumDeposit = MinimumDeposit; - type AbsoluteMinimumContribution = AbsoluteMinimumContribution; - type MinimumBlockDuration = MinimumBlockDuration; - type MaximumBlockDuration = MaximumBlockDuration; - type RefundContributorsLimit = RefundContributorsLimit; - type MaxContributors = MaxContributors; -} - -// Proxy Pallet config -parameter_types! { - // Set as 1 for testing purposes - pub const ProxyDepositBase: Balance = TaoBalance::new(1); - // Set as 1 for testing purposes - pub const ProxyDepositFactor: Balance = TaoBalance::new(1); - // Set as 20 for testing purposes - pub const MaxProxies: u32 = 20; // max num proxies per acct - // Set as 15 for testing purposes - pub const MaxPending: u32 = 15; // max blocks pending ~15min - // Set as 1 for testing purposes - pub const AnnouncementDepositBase: Balance = TaoBalance::new(1); - // Set as 1 for testing purposes - pub const AnnouncementDepositFactor: Balance = TaoBalance::new(1); -} - -impl pallet_proxy::Config for Test { - type RuntimeCall = RuntimeCall; - type Currency = Balances; - type ProxyType = subtensor_runtime_common::ProxyType; - type ProxyDepositBase = ProxyDepositBase; - type ProxyDepositFactor = ProxyDepositFactor; - type MaxProxies = MaxProxies; - type WeightInfo = pallet_proxy::weights::SubstrateWeight; - type MaxPending = MaxPending; - type CallHasher = BlakeTwo256; - type AnnouncementDepositBase = AnnouncementDepositBase; - type AnnouncementDepositFactor = AnnouncementDepositFactor; - type BlockNumberProvider = System; -} - -impl InstanceFilter for subtensor_runtime_common::ProxyType { - fn filter(&self, _c: &RuntimeCall) -> bool { - // In tests, allow all proxy types to pass through - true - } - fn is_superset(&self, o: &Self) -> bool { - match (self, o) { - (x, y) if x == y => true, - (subtensor_runtime_common::ProxyType::Any, _) => true, - _ => false, - } - } -} - -mod test_crypto { - use super::KEY_TYPE; - use sp_core::{ - U256, - sr25519::{Public as Sr25519Public, Signature as Sr25519Signature}, - }; - use sp_runtime::{ - app_crypto::{app_crypto, sr25519}, - traits::IdentifyAccount, - }; - - app_crypto!(sr25519, KEY_TYPE); - - pub struct TestAuthId; - - impl frame_system::offchain::AppCrypto for TestAuthId { - type RuntimeAppPublic = Public; - type GenericSignature = Sr25519Signature; - type GenericPublic = Sr25519Public; - } - - impl IdentifyAccount for Public { - type AccountId = U256; - - fn into_account(self) -> U256 { - let mut bytes = [0u8; 32]; - bytes.copy_from_slice(self.as_ref()); - U256::from_big_endian(&bytes) - } - } -} - -pub type TestAuthId = test_crypto::TestAuthId; - -impl pallet_drand::Config for Test { - type AuthorityId = TestAuthId; - type Verifier = pallet_drand::verifier::QuicknetVerifier; - type UnsignedPriority = ConstU64<{ 1 << 20 }>; - type HttpFetchTimeout = ConstU64<1_000>; - type WeightInfo = (); -} - -impl frame_system::offchain::SigningTypes for Test { - type Public = test_crypto::Public; - type Signature = test_crypto::Signature; -} - -pub type UncheckedExtrinsic = sp_runtime::testing::TestXt; - -impl frame_system::offchain::CreateTransactionBase for Test -where - RuntimeCall: From, -{ - type Extrinsic = UncheckedExtrinsic; - type RuntimeCall = RuntimeCall; -} - -impl frame_system::offchain::CreateBare for Test -where - RuntimeCall: From, -{ - fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic { - UncheckedExtrinsic::new_bare(call) - } -} - -static TEST_LOGS_INIT: OnceLock<()> = OnceLock::new(); - -pub fn init_logs_for_tests() { - if TEST_LOGS_INIT.get().is_some() { - return; - } - - // RUST_LOG (full syntax) or "off" if unset - let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("off")); - - // Bridge log -> tracing (ok if already set) - let _ = tracing_log::LogTracer::init(); - - // Simple formatter - let fmt_layer = tracing_subscriber::fmt::layer() - .with_ansi(false) - .with_target(true) - .with_level(true) - .without_time(); - - let _ = tracing_subscriber::registry() - .with(filter) - .with(fmt_layer) - .try_init(); - - let _ = TEST_LOGS_INIT.set(()); -} - -#[allow(dead_code)] -// Build genesis storage according to the mock runtime. -pub fn new_test_ext(block_number: BlockNumber) -> sp_io::TestExternalities { - init_logs_for_tests(); - let t = frame_system::GenesisConfig::::default() - .build_storage() - .unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(block_number)); - ext -} - -#[allow(dead_code)] -pub fn add_network(netuid: NetUid, tempo: u16, _modality: u16) { - SubtensorModule::init_new_network(netuid, tempo); - SubtensorModule::set_network_registration_allowed(netuid, true); - FirstEmissionBlockNumber::::insert(netuid, 1); - SubtokenEnabled::::insert(netuid, true); - - // make interval 1 block so tests can register by stepping 1 block. - BurnHalfLife::::insert(netuid, 1); - BurnIncreaseMult::::insert(netuid, U64F64::from_num(1)); -} - -#[allow(dead_code)] -pub fn add_balance_to_coldkey_account(coldkey: &U256, tao: TaoBalance) { - let ed = ExistentialDeposit::get(); - if tao >= ed { - let credit = SubtensorModule::mint_tao(tao); - let _ = SubtensorModule::spend_tao(coldkey, credit, tao).unwrap(); - } -} diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index be37a9227b..7f2a078359 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -16,12 +16,12 @@ mod math; mod mechanism; mod migration; pub(crate) mod mock; -pub(crate) mod mock_high_ed; mod move_stake; mod networks; mod neuron_info; mod recycle_alpha; mod registration; +mod sale_offer; mod serving; mod staking; mod staking2; diff --git a/pallets/subtensor/src/tests/sale_offer.rs b/pallets/subtensor/src/tests/sale_offer.rs new file mode 100644 index 0000000000..0950abfa56 --- /dev/null +++ b/pallets/subtensor/src/tests/sale_offer.rs @@ -0,0 +1,291 @@ +#![allow(clippy::unwrap_used)] + +use super::mock::*; +use crate::*; +use frame_support::{assert_noop, assert_ok}; +use sp_core::U256; +use subtensor_runtime_common::{NetUid, TaoBalance}; + +const SELLER: u64 = 1; +const OWNER_HOTKEY: u64 = 2; +const BUYER: u64 = 3; + +fn sale_fixture() -> (NetUid, U256, U256, U256) { + let seller = U256::from(SELLER); + let owner_hotkey = U256::from(OWNER_HOTKEY); + let buyer = U256::from(BUYER); + let netuid = add_dynamic_network(&owner_hotkey, &seller); + + (netuid, seller, owner_hotkey, buyer) +} + +fn create_offer(netuid: NetUid, seller: U256, buyer: Option) { + assert_ok!(SubtensorModule::create_sale_offer( + RuntimeOrigin::signed(seller), + netuid, + TaoBalance::from(1_000_000_000_u64), + buyer, + )); +} + +#[test] +fn create_sale_offer_stores_offer_and_freezes_keys() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, owner_hotkey, buyer) = sale_fixture(); + let price = TaoBalance::from(1_000_000_000_u64); + + assert_ok!(SubtensorModule::create_sale_offer( + RuntimeOrigin::signed(seller), + netuid, + price, + Some(buyer), + )); + + let offer = SubnetSaleOffers::::get(netuid).unwrap(); + assert_eq!(offer.netuid, netuid); + assert_eq!(offer.seller, seller); + assert_eq!(offer.authorized_buyer, Some(buyer)); + assert_eq!(offer.price, price); + assert_eq!(offer.created_at, System::block_number()); + assert!(SubnetSaleFrozenColdkeys::::contains_key(seller)); + assert!(SubnetSaleFrozenHotkeys::::contains_key(owner_hotkey)); + assert_eq!( + last_event(), + RuntimeEvent::SubtensorModule(Event::SubnetSaleOfferCreated { + seller, + netuid, + price, + authorized_buyer: Some(buyer), + }) + ); + }); +} + +#[test] +fn create_sale_offer_allows_open_offer() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, _owner_hotkey, _buyer) = sale_fixture(); + + assert_ok!(SubtensorModule::create_sale_offer( + RuntimeOrigin::signed(seller), + netuid, + TaoBalance::from(1_000_000_000_u64), + None, + )); + + let offer = SubnetSaleOffers::::get(netuid).unwrap(); + assert_eq!(offer.authorized_buyer, None); + }); +} + +#[test] +fn create_sale_offer_rejects_zero_price() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, _owner_hotkey, buyer) = sale_fixture(); + + assert_noop!( + SubtensorModule::create_sale_offer( + RuntimeOrigin::signed(seller), + netuid, + TaoBalance::from(0_u64), + Some(buyer), + ), + Error::::AmountTooLow, + ); + }); +} + +#[test] +fn create_sale_offer_rejects_missing_subnet() { + new_test_ext(1).execute_with(|| { + let seller = U256::from(SELLER); + let missing_netuid = NetUid::from(99); + + assert_noop!( + SubtensorModule::create_sale_offer( + RuntimeOrigin::signed(seller), + missing_netuid, + TaoBalance::from(1_000_000_000_u64), + None, + ), + Error::::SubnetNotExists, + ); + }); +} + +#[test] +fn create_sale_offer_rejects_non_owner() { + new_test_ext(1).execute_with(|| { + let (netuid, _seller, _owner_hotkey, buyer) = sale_fixture(); + let not_owner = U256::from(99); + + assert_noop!( + SubtensorModule::create_sale_offer( + RuntimeOrigin::signed(not_owner), + netuid, + TaoBalance::from(1_000_000_000_u64), + Some(buyer), + ), + Error::::NotSubnetOwner, + ); + }); +} + +#[test] +fn create_sale_offer_rejects_existing_offer() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, _owner_hotkey, buyer) = sale_fixture(); + create_offer(netuid, seller, Some(buyer)); + + assert_noop!( + SubtensorModule::create_sale_offer( + RuntimeOrigin::signed(seller), + netuid, + TaoBalance::from(2_000_000_000_u64), + None, + ), + Error::::SaleOfferAlreadyExists, + ); + }); +} + +#[test] +fn create_sale_offer_rejects_frozen_seller() { + new_test_ext(1).execute_with(|| { + let (first_netuid, seller, _first_owner_hotkey, buyer) = sale_fixture(); + let second_owner_hotkey = U256::from(20); + let second_netuid = add_dynamic_network(&second_owner_hotkey, &seller); + create_offer(first_netuid, seller, Some(buyer)); + + assert_noop!( + SubtensorModule::create_sale_offer( + RuntimeOrigin::signed(seller), + second_netuid, + TaoBalance::from(2_000_000_000_u64), + None, + ), + Error::::ColdkeyLockedDuringSale, + ); + }); +} + +#[test] +fn create_sale_offer_rejects_missing_owner_hotkey() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, _owner_hotkey, buyer) = sale_fixture(); + SubnetOwnerHotkey::::remove(netuid); + + assert_noop!( + SubtensorModule::create_sale_offer( + RuntimeOrigin::signed(seller), + netuid, + TaoBalance::from(1_000_000_000_u64), + Some(buyer), + ), + Error::::HotKeyAccountNotExists, + ); + }); +} + +#[test] +fn create_sale_offer_rejects_frozen_owner_hotkey() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, owner_hotkey, buyer) = sale_fixture(); + SubnetSaleFrozenHotkeys::::insert(owner_hotkey, ()); + + assert_noop!( + SubtensorModule::create_sale_offer( + RuntimeOrigin::signed(seller), + netuid, + TaoBalance::from(1_000_000_000_u64), + Some(buyer), + ), + Error::::HotkeyLockedDuringSale, + ); + }); +} + +#[test] +fn cancel_sale_offer_unfreezes_keys() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, owner_hotkey, buyer) = sale_fixture(); + create_offer(netuid, seller, Some(buyer)); + + assert_ok!(SubtensorModule::cancel_sale_offer( + RuntimeOrigin::signed(seller), + netuid, + )); + + assert!(!SubnetSaleOffers::::contains_key(netuid)); + assert!(!SubnetSaleFrozenColdkeys::::contains_key(seller)); + assert!(!SubnetSaleFrozenHotkeys::::contains_key(owner_hotkey)); + assert_eq!( + last_event(), + RuntimeEvent::SubtensorModule(Event::SubnetSaleOfferCancelled { seller, netuid }) + ); + }); +} + +#[test] +fn cancel_sale_offer_root_unfreezes_keys() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, owner_hotkey, buyer) = sale_fixture(); + create_offer(netuid, seller, Some(buyer)); + + assert_ok!(SubtensorModule::cancel_sale_offer( + RuntimeOrigin::root(), + netuid, + )); + + assert!(!SubnetSaleOffers::::contains_key(netuid)); + assert!(!SubnetSaleFrozenColdkeys::::contains_key(seller)); + assert!(!SubnetSaleFrozenHotkeys::::contains_key(owner_hotkey)); + assert_eq!( + last_event(), + RuntimeEvent::SubtensorModule(Event::SubnetSaleOfferCancelled { seller, netuid }) + ); + }); +} + +#[test] +fn cancel_sale_offer_rejects_missing_offer() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, _owner_hotkey, _buyer) = sale_fixture(); + + assert_noop!( + SubtensorModule::cancel_sale_offer(RuntimeOrigin::signed(seller), netuid), + Error::::SaleOfferNotFound, + ); + }); +} + +#[test] +fn cancel_sale_offer_rejects_non_seller() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, _owner_hotkey, buyer) = sale_fixture(); + let not_seller = U256::from(99); + create_offer(netuid, seller, Some(buyer)); + + assert_noop!( + SubtensorModule::cancel_sale_offer(RuntimeOrigin::signed(not_seller), netuid), + Error::::NotSubnetOwner, + ); + }); +} + +#[test] +fn remove_network_cleans_sale_offer() { + new_test_ext(1).execute_with(|| { + let (netuid, seller, owner_hotkey, buyer) = sale_fixture(); + create_offer(netuid, seller, Some(buyer)); + + assert_ok!(SubtensorModule::root_dissolve_network( + RuntimeOrigin::root(), + netuid, + )); + + assert!(!SubnetSaleOffers::::contains_key(netuid)); + assert!(!SubnetSaleFrozenColdkeys::::contains_key(seller)); + assert!(!SubnetSaleFrozenHotkeys::::contains_key(owner_hotkey)); + }); +} diff --git a/pallets/subtensor/src/tests/tao.rs b/pallets/subtensor/src/tests/tao.rs index b79b80e3f3..ab8cf40839 100644 --- a/pallets/subtensor/src/tests/tao.rs +++ b/pallets/subtensor/src/tests/tao.rs @@ -6,8 +6,7 @@ clippy::expect_used )] -use super::mock_high_ed::*; -use crate::tests::mock_high_ed; +use super::mock::*; use crate::*; use frame_support::{ assert_noop, assert_ok, @@ -21,6 +20,11 @@ use sp_runtime::traits::{AccountIdConversion, Zero}; use subtensor_runtime_common::TaoBalance; const MAX_TAO_ISSUANCE: u64 = 21_000_000_000_000_000_u64; +const HIGH_EXISTENTIAL_DEPOSIT: TaoBalance = TaoBalance::new(100); + +fn new_high_ed_test_ext(block_number: BlockNumber) -> TestExternalitiesWithExistentialDeposit { + test_ext_with_existential_deposit(block_number, HIGH_EXISTENTIAL_DEPOSIT) +} /// Helper: balances-pallet total issuance. fn balances_total_issuance() -> TaoBalance { @@ -48,7 +52,7 @@ fn total_balance(account: &U256) -> TaoBalance { #[test] fn test_transfer_tao_normal_case() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(1); let dest = U256::from(2); @@ -69,7 +73,7 @@ fn test_transfer_tao_normal_case() { #[test] fn test_transfer_tao_zero_balance_zero_amount() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(10_001); let dest = U256::from(10_002); @@ -86,7 +90,7 @@ fn test_transfer_tao_zero_balance_zero_amount() { #[test] fn test_transfer_tao_zero_balance_non_zero_amount_fails() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(10_011); let dest = U256::from(10_012); @@ -104,7 +108,7 @@ fn test_transfer_tao_zero_balance_non_zero_amount_fails() { #[test] fn test_transfer_tao_amount_greater_than_transferrable_fails() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(1); let dest = U256::from(2); @@ -120,7 +124,7 @@ fn test_transfer_tao_amount_greater_than_transferrable_fails() { #[test] fn test_transfer_tao_transfer_exactly_transferrable_succeeds() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(1); let dest = U256::from(2); @@ -137,7 +141,7 @@ fn test_transfer_tao_transfer_exactly_transferrable_succeeds() { #[test] fn test_transfer_tao_can_reap_origin_when_amount_brings_it_below_ed() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(1); let dest = U256::from(2); @@ -161,7 +165,7 @@ fn test_transfer_tao_can_reap_origin_when_amount_brings_it_below_ed() { #[test] fn test_transfer_tao_to_self_is_ok_and_no_net_balance_change() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let who = U256::from(1); let before = total_balance(&who); let amount = reducible_balance(&who).min(10.into()); @@ -178,7 +182,7 @@ fn test_transfer_tao_to_self_is_ok_and_no_net_balance_change() { #[test] fn test_transfer_all_tao_and_kill_normal_case() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(1); let dest = U256::from(2); @@ -206,7 +210,7 @@ fn test_transfer_all_tao_and_kill_normal_case() { #[test] fn test_transfer_all_tao_and_kill_non_existing_origin_is_noop() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(20_001); let dest = U256::from(20_002); @@ -224,7 +228,7 @@ fn test_transfer_all_tao_and_kill_non_existing_origin_is_noop() { #[test] fn test_transfer_all_tao_and_kill_preexisting_destination() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(1); let dest = U256::from(2); @@ -247,7 +251,7 @@ fn test_transfer_all_tao_and_kill_preexisting_destination() { #[test] fn test_transfer_all_tao_and_kill_to_self_is_noop() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let who = U256::from(1); let before_total = total_balance(&who); let before_reducible = reducible_balance(&who); @@ -265,7 +269,7 @@ fn test_transfer_all_tao_and_kill_to_self_is_noop() { #[test] fn test_burn_tao_increases_burn_address_balance() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let coldkey = U256::from(1); let burn_address: U256 = ::BurnAccountId::get().into_account_truncating(); @@ -285,7 +289,7 @@ fn test_burn_tao_increases_burn_address_balance() { #[test] fn test_burn_tao_zero_amount_is_ok() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let coldkey = U256::from(1); let burn_address: U256 = ::BurnAccountId::get().into_account_truncating(); @@ -301,7 +305,7 @@ fn test_burn_tao_zero_amount_is_ok() { #[test] fn test_burn_tao_insufficient_balance_fails() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let coldkey = U256::from(30_001); assert_noop!( @@ -317,7 +321,7 @@ fn test_burn_tao_insufficient_balance_fails() { #[test] fn test_recycle_tao_reduces_both_balances_and_subtensor_total_issuance() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let coldkey = U256::from(1); let max_preserving = SubtensorModule::get_coldkey_balance(&coldkey); let amount = max_preserving.min(10.into()); @@ -343,7 +347,7 @@ fn test_recycle_tao_reduces_both_balances_and_subtensor_total_issuance() { #[test] fn test_recycle_tao_amount_greater_than_max_preserving_fails() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let coldkey = U256::from(1); let max_preserving: u64 = ::Currency::reducible_balance( &coldkey, @@ -365,7 +369,7 @@ fn test_recycle_tao_amount_greater_than_max_preserving_fails() { #[test] fn test_recycle_tao_zero_amount_keeps_issuance_equal() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let coldkey = U256::from(1); let balances_before = balances_total_issuance(); let subtensor_before = subtensor_total_issuance(); @@ -385,7 +389,7 @@ fn test_recycle_tao_zero_amount_keeps_issuance_equal() { /// and recycle should reduce both by the same amount. #[test] fn test_total_issuance_subtensor_matches_balances_across_tao_operations() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let a = U256::from(1); let b = U256::from(2); @@ -423,7 +427,7 @@ fn test_total_issuance_subtensor_matches_balances_across_tao_operations() { /// SubtensorModule::TotalIssuance. #[test] fn test_mint_tao_increases_total_issuance_in_balances_and_subtensor() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let amount = TaoBalance::from(123); let balances_before = balances_total_issuance(); @@ -443,7 +447,7 @@ fn test_mint_tao_increases_total_issuance_in_balances_and_subtensor() { #[test] fn test_mint_tao_zero_amount() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let balances_before = balances_total_issuance(); let subtensor_before = subtensor_total_issuance(); @@ -457,7 +461,7 @@ fn test_mint_tao_zero_amount() { #[test] fn test_mint_tao_respects_max_issuance_cap_in_balances() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { // We cannot directly force balances-pallet issuance above the cap in every mock, // but we *can* set subtensor's mirror and still verify that mint_tao uses the // balances-pallet total issuance as its source of truth. @@ -477,7 +481,7 @@ fn test_mint_tao_respects_max_issuance_cap_in_balances() { #[test] fn test_transfer_tao_reaps_origin() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(1); let dest = U256::from(2); @@ -502,7 +506,7 @@ fn test_transfer_tao_reaps_origin() { #[test] fn test_recycle_tao_cannot_cross_preserve_threshold_in_high_ed_runtime() { - new_test_ext(1).execute_with(|| { + new_high_ed_test_ext(1).execute_with(|| { let origin = U256::from(1); let max_preserving = diff --git a/pallets/subtensor/src/weights.rs b/pallets/subtensor/src/weights.rs index e6e1d497de..89ed188a9c 100644 --- a/pallets/subtensor/src/weights.rs +++ b/pallets/subtensor/src/weights.rs @@ -80,6 +80,8 @@ pub trait WeightInfo { fn remove_stake_full_limit() -> Weight; fn register_leased_network(k: u32, ) -> Weight; fn terminate_lease(k: u32, ) -> Weight; + fn create_sale_offer() -> Weight; + fn cancel_sale_offer() -> Weight; fn update_symbol() -> Weight; fn commit_timelocked_weights() -> Weight; fn set_coldkey_auto_stake_hotkey() -> Weight; @@ -2150,6 +2152,36 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) .saturating_add(Weight::from_parts(0, 2514).saturating_mul(k.into())) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetOwner` (r:1 w:0) + /// Proof: `SubtensorModule::SubnetOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetSaleOffers` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetSaleOffers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetSaleFrozenColdkeys` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetSaleFrozenColdkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetOwnerHotkey` (r:1 w:0) + /// Proof: `SubtensorModule::SubnetOwnerHotkey` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetSaleFrozenHotkeys` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetSaleFrozenHotkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn create_sale_offer() -> Weight { + Weight::from_parts(25_000_000, 0) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `SubtensorModule::SubnetSaleOffers` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetSaleOffers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetOwnerHotkey` (r:1 w:0) + /// Proof: `SubtensorModule::SubnetOwnerHotkey` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetSaleFrozenColdkeys` (r:0 w:1) + /// Proof: `SubtensorModule::SubnetSaleFrozenColdkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetSaleFrozenHotkeys` (r:0 w:1) + /// Proof: `SubtensorModule::SubnetSaleFrozenHotkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_sale_offer() -> Weight { + Weight::from_parts(15_000_000, 0) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } /// Storage: `SubtensorModule::SubnetOwner` (r:1 w:0) /// Proof: `SubtensorModule::SubnetOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TokenSymbol` (r:3 w:1) @@ -4508,6 +4540,36 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) .saturating_add(Weight::from_parts(0, 2514).saturating_mul(k.into())) } + /// Storage: `SubtensorModule::NetworksAdded` (r:1 w:0) + /// Proof: `SubtensorModule::NetworksAdded` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetOwner` (r:1 w:0) + /// Proof: `SubtensorModule::SubnetOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetSaleOffers` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetSaleOffers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetSaleFrozenColdkeys` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetSaleFrozenColdkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetOwnerHotkey` (r:1 w:0) + /// Proof: `SubtensorModule::SubnetOwnerHotkey` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetSaleFrozenHotkeys` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetSaleFrozenHotkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn create_sale_offer() -> Weight { + Weight::from_parts(25_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `SubtensorModule::SubnetSaleOffers` (r:1 w:1) + /// Proof: `SubtensorModule::SubnetSaleOffers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetOwnerHotkey` (r:1 w:0) + /// Proof: `SubtensorModule::SubnetOwnerHotkey` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetSaleFrozenColdkeys` (r:0 w:1) + /// Proof: `SubtensorModule::SubnetSaleFrozenColdkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `SubtensorModule::SubnetSaleFrozenHotkeys` (r:0 w:1) + /// Proof: `SubtensorModule::SubnetSaleFrozenHotkeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn cancel_sale_offer() -> Weight { + Weight::from_parts(15_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } /// Storage: `SubtensorModule::SubnetOwner` (r:1 w:0) /// Proof: `SubtensorModule::SubnetOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `SubtensorModule::TokenSymbol` (r:3 w:1) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index df8d3a1a4a..198947679f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -291,6 +291,11 @@ impl Contains for NoNestingCallFilter { } } +pub type DispatchExtension = ( + pallet_subtensor::CheckColdkeySwap, + pallet_subtensor::CheckSubnetSale, +); + // Configure FRAME pallets to include in runtime. impl frame_system::Config for Runtime { @@ -347,7 +352,7 @@ impl frame_system::Config for Runtime { type PostInherents = (); type PostTransactions = (); type ExtensionsWeightInfo = frame_system::SubstrateExtensionsWeight; - type DispatchExtension = pallet_subtensor::CheckColdkeySwap; + type DispatchExtension = DispatchExtension; } impl pallet_insecure_randomness_collective_flip::Config for Runtime {}