Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion eco-tests/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,14 @@ impl system::Config for Test {
type MaxConsumers = frame_support::traits::ConstU32<16>;
type Nonce = u64;
type Block = Block;
type DispatchExtension = pallet_subtensor::CheckColdkeySwap<Test>;
type DispatchExtension = (
pallet_subtensor::CheckColdkeySwap<Test>,
pallet_subtensor::CheckWeights<Test>,
pallet_subtensor::CheckRateLimits<Test>,
pallet_subtensor::CheckDelegateTake<Test>,
pallet_subtensor::CheckServingEndpoints<Test>,
pallet_subtensor::CheckEvmKeyAssociation<Test>,
);
}

parameter_types! {
Expand Down
573 changes: 177 additions & 396 deletions pallets/subtensor/src/extensions/subtensor.rs

Large diffs are not rendered by default.

41 changes: 29 additions & 12 deletions pallets/subtensor/src/guards/check_coldkey_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,15 @@ where
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
use super::CheckColdkeySwap;
use crate::{ColdkeySwapAnnouncements, ColdkeySwapDisputes, Error, tests::mock::*};
use frame_support::{BoundedVec, assert_ok};
use frame_support::{
BoundedVec, assert_ok, dispatch::DispatchResultWithPostInfo, traits::ExtendedDispatchable,
};
use frame_system::Call as SystemCall;
use pallet_subtensor_proxy::Call as ProxyCall;
use sp_core::U256;
use sp_runtime::traits::{Dispatchable, Hash};
use sp_runtime::traits::Hash;
use subtensor_runtime_common::{ProxyType, TaoBalance};

type HashingOf<T> = <T as frame_system::Config>::Hashing;
Expand Down Expand Up @@ -154,11 +157,17 @@ mod tests {
let _ = SubtensorModule::spend_tao(coldkey, credit, tao).unwrap();
}

fn dispatch_with_ext(call: RuntimeCall, origin: RuntimeOrigin) -> DispatchResultWithPostInfo {
<CheckColdkeySwap<Test> as ExtendedDispatchable<RuntimeCall>>::dispatch_with_extension(
origin, call,
)
}

#[test]
fn no_active_swap_allows_calls() {
new_test_ext(1).execute_with(|| {
let who = U256::from(1);
assert_ok!(remark_call().dispatch(RuntimeOrigin::signed(who)));
assert_ok!(dispatch_with_ext(remark_call(), RuntimeOrigin::signed(who)));
});
}

Expand All @@ -167,8 +176,7 @@ mod tests {
new_test_ext(1).execute_with(|| {
let who = U256::from(1);
setup_swap_disputed(&who);

assert_ok!(remark_call().dispatch(RuntimeOrigin::none()));
assert_ok!(dispatch_with_ext(remark_call(), RuntimeOrigin::none()));
});
}

Expand All @@ -177,8 +185,7 @@ mod tests {
new_test_ext(1).execute_with(|| {
let who = U256::from(1);
setup_swap_disputed(&who);

assert_ok!(remark_call().dispatch(RuntimeOrigin::root()));
assert_ok!(dispatch_with_ext(remark_call(), RuntimeOrigin::root()));
});
}

Expand All @@ -190,7 +197,9 @@ mod tests {

for call in forbidden_calls() {
assert_eq!(
call.dispatch(RuntimeOrigin::signed(who)).unwrap_err().error,
dispatch_with_ext(call, RuntimeOrigin::signed(who))
.unwrap_err()
.error,
Error::<Test>::ColdkeySwapAnnounced.into()
);
}
Expand All @@ -204,7 +213,7 @@ mod tests {
setup_swap_announced(&who);

for call in authorized_calls() {
if let Err(err) = call.dispatch(RuntimeOrigin::signed(who)) {
if let Err(err) = dispatch_with_ext(call, RuntimeOrigin::signed(who)) {
assert_ne!(
err.error,
Error::<Test>::ColdkeySwapAnnounced.into(),
Expand All @@ -229,7 +238,9 @@ mod tests {

for call in all_calls {
assert_eq!(
call.dispatch(RuntimeOrigin::signed(who)).unwrap_err().error,
dispatch_with_ext(call, RuntimeOrigin::signed(who))
.unwrap_err()
.error,
Error::<Test>::ColdkeySwapDisputed.into()
);
}
Expand Down Expand Up @@ -265,7 +276,10 @@ mod tests {
});

// The outer proxy call itself succeeds
assert_ok!(proxy_call.dispatch(RuntimeOrigin::signed(delegate)));
assert_ok!(dispatch_with_ext(
proxy_call,
RuntimeOrigin::signed(delegate)
));

// The inner call was blocked — check via LastCallResult storage.
assert_eq!(
Expand Down Expand Up @@ -315,7 +329,10 @@ mod tests {
call: Box::new(inner_proxy),
});

assert_ok!(outer_proxy.dispatch(RuntimeOrigin::signed(delegate2)));
assert_ok!(dispatch_with_ext(
outer_proxy,
RuntimeOrigin::signed(delegate2)
));

// The innermost call (remark as real) was blocked.
assert_eq!(
Expand Down
191 changes: 191 additions & 0 deletions pallets/subtensor/src/guards/check_delegate_take.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use crate::{Call, Config, Error, Pallet};
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<T> = <T as frame_system::Config>::RuntimeCall;
type DispatchableOriginOf<T> = <CallOf<T> as Dispatchable>::RuntimeOrigin;

/// Dispatch extension for delegate-take bounds and ownership preconditions.
///
/// Signed increase/decrease take calls are checked before dispatch; unrelated
/// calls and non-signed origins pass through.
pub struct CheckDelegateTake<T: Config>(PhantomData<T>);

impl<T: Config> CheckDelegateTake<T> {
pub fn check(who: &T::AccountId, call: &Call<T>) -> Result<(), Error<T>> {
match call {
Call::increase_take { hotkey, take } | Call::decrease_take { hotkey, take } => {
if *take < Pallet::<T>::get_min_delegate_take() {
return Err(Error::<T>::DelegateTakeTooLow);
}
if *take > Pallet::<T>::get_max_delegate_take() {
return Err(Error::<T>::DelegateTakeTooHigh);
}
Pallet::<T>::do_take_checks(who, hotkey)
}
_ => Ok(()),
}
}
}

impl<T> DispatchExtension<CallOf<T>> for CheckDelegateTake<T>
where
T: Config,
CallOf<T>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>>,
DispatchableOriginOf<T>: OriginTrait<AccountId = T::AccountId>,
{
type Pre = ();

fn weight(_call: &CallOf<T>) -> Weight {
T::DbWeight::get().reads(3)
}

fn pre_dispatch(
origin: &DispatchableOriginOf<T>,
call: &CallOf<T>,
) -> Result<Self::Pre, DispatchErrorWithPostInfo> {
let Some(who) = origin.as_signer() else {
return Ok(());
};

let Some(call) = call.is_sub_type() else {
return Ok(());
};

Self::check(who, call).map_err(Into::into)
}
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::{Error, tests::mock::*};
use frame_support::{
assert_ok, dispatch::DispatchResultWithPostInfo, traits::ExtendedDispatchable,
};
use sp_core::U256;
use sp_runtime::DispatchError;

fn increase_take_call(hotkey: U256, take: u16) -> RuntimeCall {
RuntimeCall::SubtensorModule(SubtensorCall::increase_take { hotkey, take })
}

fn decrease_take_call(hotkey: U256, take: u16) -> RuntimeCall {
RuntimeCall::SubtensorModule(SubtensorCall::decrease_take { hotkey, take })
}

fn dispatch_with_ext(call: RuntimeCall, origin: RuntimeOrigin) -> DispatchResultWithPostInfo {
<CheckDelegateTake<Test> as ExtendedDispatchable<RuntimeCall>>::dispatch_with_extension(
origin, call,
)
}

fn err(result: DispatchResultWithPostInfo) -> DispatchError {
result.unwrap_err().error
}

#[test]
fn accepts_owner_with_valid_take() {
new_test_ext(0).execute_with(|| {
let owner = U256::from(1);
let hotkey = U256::from(2);
crate::Owner::<Test>::insert(hotkey, owner);

for call in [
increase_take_call(hotkey, SubtensorModule::get_max_delegate_take()),
decrease_take_call(hotkey, SubtensorModule::get_min_delegate_take()),
] {
assert_ok!(dispatch_with_ext(call, RuntimeOrigin::signed(owner)));
}
});
}

#[test]
fn rejects_take_too_low() {
new_test_ext(0).execute_with(|| {
let owner = U256::from(1);
let hotkey = U256::from(2);
crate::Owner::<Test>::insert(hotkey, owner);

let take = SubtensorModule::get_min_delegate_take() - 1;

for call in [
increase_take_call(hotkey, take),
decrease_take_call(hotkey, take),
] {
assert_eq!(
err(dispatch_with_ext(call, RuntimeOrigin::signed(owner))),
Error::<Test>::DelegateTakeTooLow.into()
);
}
});
}

#[test]
fn rejects_take_too_high() {
new_test_ext(0).execute_with(|| {
let owner = U256::from(1);
let hotkey = U256::from(2);
crate::Owner::<Test>::insert(hotkey, owner);

let take = SubtensorModule::get_max_delegate_take() + 1;

for call in [
increase_take_call(hotkey, take),
decrease_take_call(hotkey, take),
] {
assert_eq!(
err(dispatch_with_ext(call, RuntimeOrigin::signed(owner))),
Error::<Test>::DelegateTakeTooHigh.into()
);
}
});
}

#[test]
fn rejects_non_owner() {
new_test_ext(0).execute_with(|| {
let owner = U256::from(1);
let other = U256::from(2);
let hotkey = U256::from(3);
crate::Owner::<Test>::insert(hotkey, owner);

let take = SubtensorModule::get_max_delegate_take();

for call in [
increase_take_call(hotkey, take),
decrease_take_call(hotkey, take),
] {
assert_eq!(
err(dispatch_with_ext(call, RuntimeOrigin::signed(other))),
Error::<Test>::NonAssociatedColdKey.into()
);
}
});
}

#[test]
fn rejects_missing_hotkey_owner() {
new_test_ext(0).execute_with(|| {
let owner = U256::from(1);
let hotkey = U256::from(99);
let take = SubtensorModule::get_max_delegate_take();

for call in [
increase_take_call(hotkey, take),
decrease_take_call(hotkey, take),
] {
assert_eq!(
err(dispatch_with_ext(call, RuntimeOrigin::signed(owner))),
Error::<Test>::HotKeyAccountNotExists.into()
);
}
});
}
}
Loading
Loading