Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add a tremendous amount of tests #913

Open
wants to merge 4 commits into
base: drew/slashing-updates
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pallets/multi-asset-delegation/src/functions/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ impl<T: Config> Pallet<T> {
*operator_updates.entry(delegation_key).or_default() += request.amount;
indices_to_remove.push(idx);
}
println!("indices_to_remove: {:?}", indices_to_remove);

ensure!(!indices_to_remove.is_empty(), Error::<T>::BondLessNotReady);
Ok((deposit_updates, delegation_updates, operator_updates, indices_to_remove))
}
Expand Down
5 changes: 4 additions & 1 deletion pallets/multi-asset-delegation/src/functions/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,10 @@ impl<T: Config> Pallet<T> {
pub fn process_go_offline(who: &T::AccountId) -> Result<(), DispatchError> {
let mut operator = Operators::<T>::get(who).ok_or(Error::<T>::NotAnOperator)?;
ensure!(operator.status == OperatorStatus::Active, Error::<T>::NotActiveOperator);

ensure!(
!T::ServiceManager::has_active_services(who),
Error::<T>::CannotGoOfflineWithActiveServices
);
operator.status = OperatorStatus::Inactive;
Operators::<T>::insert(who, operator);

Expand Down
5 changes: 2 additions & 3 deletions pallets/multi-asset-delegation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,6 @@ pub mod pallet {
/// The origin with privileged access
type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;

/// The address that receives slashed funds
type SlashedAmountRecipient: Get<Self::AccountId>;

/// A type that implements the `EvmRunner` trait for the execution of EVM
/// transactions.
type EvmRunner: tangle_primitives::services::EvmRunner<Self>;
Expand Down Expand Up @@ -456,6 +453,8 @@ pub mod pallet {
OverflowRisk,
/// Delegator is not a nominator
NotNominator,
/// Operator has active services and can't exit
CannotGoOfflineWithActiveServices,
}

/// Hooks for the pallet.
Expand Down
7 changes: 5 additions & 2 deletions pallets/multi-asset-delegation/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,15 +306,19 @@ parameter_types! {
pub const MinOperatorBondAmount: u64 = 10_000;
pub const BondDuration: u32 = 10;
pub PID: PalletId = PalletId(*b"PotStake");
pub SlashedAmountRecipient : AccountId = AccountKeyring::Alice.into();

#[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub const MaxDelegatorBlueprints : u32 = 50;

#[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub const MaxOperatorBlueprints : u32 = 50;

#[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub const MaxWithdrawRequests: u32 = 50;

#[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub const MaxUnstakeRequests: u32 = 50;

#[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub const MaxDelegations: u32 = 50;
}
Expand Down Expand Up @@ -409,7 +413,6 @@ impl pallet_multi_asset_delegation::Config for Runtime {
type MaxWithdrawRequests = MaxWithdrawRequests;
type MaxUnstakeRequests = MaxUnstakeRequests;
type MaxDelegations = MaxDelegations;
type SlashedAmountRecipient = SlashedAmountRecipient;
type EvmRunner = MockedEvmRunner;
type EvmGasWeightMapping = PalletEVMGasWeightMapping;
type EvmAddressMapping = PalletEVMAddressMapping;
Expand Down
1 change: 0 additions & 1 deletion pallets/multi-asset-delegation/src/tests/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ use sp_keyring::AccountKeyring::Bob;
use sp_runtime::{ArithmeticError, DispatchError};
use tangle_primitives::services::{Asset, EvmAddressMapping};

// helper function
pub fn create_and_mint_tokens(
asset_id: AssetId,
recipient: <Runtime as frame_system::Config>::AccountId,
Expand Down
6 changes: 5 additions & 1 deletion pallets/rewards/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,15 +322,19 @@ parameter_types! {
pub const MinOperatorBondAmount: u64 = 10_000;
pub const BondDuration: u32 = 10;
pub PID: PalletId = PalletId(*b"PotStake");
pub SlashedAmountRecipient : AccountId = AccountKeyring::Alice.into();

#[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub const MaxDelegatorBlueprints : u32 = 50;

#[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub const MaxOperatorBlueprints : u32 = 50;

#[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub const MaxWithdrawRequests: u32 = 50;

#[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub const MaxUnstakeRequests: u32 = 50;

#[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub const MaxDelegations: u32 = 50;
}
Expand Down
3 changes: 3 additions & 0 deletions pallets/services/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ precompile-utils = { workspace = true }

pallet-session = { workspace = true }
pallet-staking = { workspace = true }
pallet-multi-asset-delegation = { workspace = true }
sp-staking = { workspace = true }
sp-weights = { workspace = true }
frame-election-provider-support = { workspace = true }

[features]
Expand All @@ -89,6 +91,7 @@ std = [
"sp-std/std",
"sp-io/std",
"sp-staking/std",
"sp-weights/std",
"tangle-primitives/std",
"pallet-balances/std",
"pallet-timestamp/std",
Expand Down
6 changes: 2 additions & 4 deletions pallets/services/src/functions/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ impl<T: Config> Pallet<T> {
Error::<T>::OperatorNotActive
);

let already_registered = Operators::<T>::contains_key(blueprint_id, &operator);
ensure!(!already_registered, Error::<T>::AlreadyRegistered);
blueprint
.type_check_registration(&registration_args)
.map_err(Error::<T>::TypeCheck)?;
Expand Down Expand Up @@ -76,13 +74,13 @@ impl<T: Config> Pallet<T> {
Ok(p) => {
p.blueprints
.try_insert(blueprint_id)
.map_err(|_| Error::<T>::MaxServicesPerProviderExceeded)?;
.map_err(|_| Error::<T>::MaxBlueprintsPerOperatorExceeded)?;
},
Err(_) => {
let mut blueprints = BoundedBTreeSet::new();
blueprints
.try_insert(blueprint_id)
.map_err(|_| Error::<T>::MaxServicesPerProviderExceeded)?;
.map_err(|_| Error::<T>::MaxBlueprintsPerOperatorExceeded)?;
*profile = Ok(OperatorProfile { blueprints, ..Default::default() });
},
};
Expand Down
3 changes: 2 additions & 1 deletion pallets/services/src/functions/slash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ impl<T: Config> Pallet<T> {
// Process all delegator slashes
for (delegator, asset, slash_amount) in unapplied_slash.clone().others {
// Transfer slashed assets to treasury
match asset {
match asset.clone() {
Asset::Custom(asset_id) => {
T::Fungibles::transfer(
asset_id,
Expand Down Expand Up @@ -184,6 +184,7 @@ impl<T: Config> Pallet<T> {
Self::deposit_event(Event::DelegatorSlashed {
delegator: delegator.clone(),
amount: slash_amount,
asset,
service_id: unapplied_slash.service_id,
blueprint_id: unapplied_slash.blueprint_id,
era: unapplied_slash.era,
Expand Down
4 changes: 4 additions & 0 deletions pallets/services/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ impl<T: crate::Config> ServiceManager<T::AccountId, BalanceOf<T>> for crate::Pal
.map_or(true, |profile| profile.services.is_empty() && profile.blueprints.is_empty())
}

fn has_active_services(operator: &T::AccountId) -> bool {
OperatorsProfile::<T>::get(operator).map_or(false, |profile| !profile.services.is_empty())
}

fn get_blueprints_by_operator(operator: &T::AccountId) -> Vec<BlueprintId> {
OperatorsProfile::<T>::get(operator)
.map_or(vec![], |profile| profile.blueprints.into_iter().collect())
Expand Down
100 changes: 95 additions & 5 deletions pallets/services/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ use sp_runtime::{
DispatchResult,
};
use tangle_primitives::{
services::{AssetSecurityCommitment, AssetSecurityRequirement, MembershipModel},
services::{
AssetSecurityCommitment, AssetSecurityRequirement, MembershipModel, UnappliedSlash,
},
traits::MultiAssetDelegationInfo,
BlueprintId, InstanceId, JobCallId, ServiceRequestId,
};
Expand Down Expand Up @@ -70,7 +72,7 @@ pub mod module {
};
use sp_core::H160;
use sp_runtime::{traits::MaybeSerializeDeserialize, Percent};
use sp_std::vec::Vec;
use sp_std::{collections::btree_set::BTreeSet, vec::Vec};
use tangle_primitives::services::*;

#[pallet::config]
Expand Down Expand Up @@ -261,6 +263,8 @@ pub mod module {
BlueprintCreationInterrupted,
/// The caller is already registered as a operator.
AlreadyRegistered,
/// The caller is registering with a key that is already registered
DuplicateKey,
/// The caller does not have the requirements to be a operator.
InvalidRegistrationInput,
/// The Operator is not allowed to unregister.
Expand Down Expand Up @@ -313,10 +317,18 @@ pub mod module {
EVMAbiDecode,
/// Operator profile not found.
OperatorProfileNotFound,
/// Maximum number of services per Provider reached.
MaxServicesPerProviderExceeded,
/// Maximum number of services per operator reached.
MaxServicesPerOperatorExceeded,
/// Maximum number of blueprints registered by the operator reached.
MaxBlueprintsPerOperatorExceeded,
/// The operator is not active, ensure operator status is ACTIVE in multi-asset-delegation
OperatorNotActive,
/// Duplicate operator registration.
DuplicateOperator,
/// Too many operators provided for the service's membership model
TooManyOperators,
/// Too few operators provided for the service's membership model
TooFewOperators,
/// No assets provided for the service, at least one asset is required.
NoAssetsProvided,
/// Duplicate assets provided
Expand Down Expand Up @@ -383,6 +395,14 @@ pub mod module {
OnOperatorLeaveFailure,
/// Operator is a member or has already joined the service
AlreadyJoined,
/// Caller is not an operator of the service
NotAnOperator,
/// Submitted result is empty
InvalidResultFormat,
/// Invalid slash percentage
InvalidSlashPercentage,
/// Invalid key (zero byte ECDSA key provided)
InvalidKey,
}

#[pallet::event]
Expand Down Expand Up @@ -570,6 +590,8 @@ pub mod module {
delegator: T::AccountId,
/// The amount of the slash.
amount: BalanceOf<T>,
/// The asset being slashed.
asset: Asset<T::AssetId>,
/// Service ID
service_id: u64,
/// Blueprint ID
Expand Down Expand Up @@ -890,12 +912,27 @@ pub mod module {
#[pallet::weight(T::WeightInfo::register())]
pub fn register(
origin: OriginFor<T>,
#[pallet::compact] blueprint_id: u64,
#[pallet::compact] blueprint_id: BlueprintId,
preferences: OperatorPreferences,
registration_args: Vec<Field<T::Constraints, T::AccountId>>,
#[pallet::compact] value: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let caller = ensure_signed(origin)?;
// Validate the operator preferences
ensure!(preferences.key != [0u8; 65], Error::<T>::InvalidKey);
// Check if operator is already registered for this blueprint
ensure!(
!Operators::<T>::contains_key(blueprint_id, &caller),
Error::<T>::AlreadyRegistered
);

// Check if the key is already in use by another operator
for (operator, prefs) in Operators::<T>::iter_prefix(blueprint_id) {
if operator != caller && prefs.key == preferences.key {
return Err(Error::<T>::DuplicateKey.into());
}
}

Self::do_register(&caller, blueprint_id, preferences, registration_args, value)?;
Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes })
}
Expand Down Expand Up @@ -1040,6 +1077,32 @@ pub mod module {
membership_model: MembershipModel,
) -> DispatchResultWithPostInfo {
let caller = ensure_signed(origin)?;
// Ensure all operators are active
for operator in operators.iter() {
ensure!(
T::OperatorDelegationManager::is_operator_active(&operator),
Error::<T>::OperatorNotActive,
);
}

// Ensure no duplicate operators
let mut seen_operators = BTreeSet::new();
for operator in operators.iter() {
ensure!(seen_operators.insert(operator), Error::<T>::DuplicateOperator);
}

// Check that the number of operators doesn't exceed the membership model max
match membership_model {
MembershipModel::Fixed { min_operators } => {
ensure!(operators.len() >= min_operators as usize, Error::<T>::TooFewOperators);
},
MembershipModel::Dynamic { min_operators, max_operators } => {
ensure!(operators.len() >= min_operators as usize, Error::<T>::TooFewOperators);
if let Some(max_ops) = max_operators {
ensure!(operators.len() <= max_ops as usize, Error::<T>::TooManyOperators);
}
},
}

Self::do_request(
caller,
Expand Down Expand Up @@ -1149,6 +1212,28 @@ pub mod module {
let caller = ensure_signed(origin)?;
let service = Self::services(service_id)?;
ensure!(service.owner == caller, DispatchError::BadOrigin);

// Apply any unapplied slashes for this service before termination
let current_era = T::OperatorDelegationManager::get_current_round();
let last_era = current_era.saturating_sub(1);

// Get slashes from current and last era
let current_slashes: Vec<_> = UnappliedSlashes::<T>::iter_prefix(current_era)
.filter(|(_, slash)| slash.service_id == service_id)
.collect();
let last_slashes: Vec<_> = UnappliedSlashes::<T>::iter_prefix(last_era)
.filter(|(_, slash)| slash.service_id == service_id)
.collect();

// Apply all slashes
for (_, slash) in current_slashes.into_iter().chain(last_slashes) {
Self::apply_slash(slash)?;
}

// Clean up storage
let _ = UnappliedSlashes::<T>::clear_prefix(current_era, u32::MAX, None);
let _ = UnappliedSlashes::<T>::clear_prefix(last_era, u32::MAX, None);

let removed = UserServices::<T>::try_mutate(&caller, |service_ids| {
Result::<_, Error<T>>::Ok(service_ids.remove(&service_id))
})?;
Expand Down Expand Up @@ -1287,6 +1372,8 @@ pub mod module {
.get(usize::from(job_call.job))
.ok_or(Error::<T>::JobDefinitionNotFound)?;

ensure!(!result.is_empty(), Error::<T>::InvalidResultFormat);

let bounded_result = BoundedVec::<_, MaxFieldsOf<T>>::try_from(result.clone())
.map_err(|_| Error::<T>::MaxFieldsExceeded)?;

Expand Down Expand Up @@ -1354,6 +1441,9 @@ pub mod module {
let slashing_origin = maybe_slashing_origin.ok_or(Error::<T>::NoSlashingOrigin)?;
ensure!(slashing_origin == caller, DispatchError::BadOrigin);

// Ensure slash percent is greater than 0
ensure!(!percent.is_zero(), Error::<T>::InvalidSlashPercentage);

// Verify offender is an operator for this service
ensure!(
service.native_asset_security.iter().any(|(op, _)| op == &offender),
Expand Down
Loading