Skip to content

Commit

Permalink
feat: add native delegation / native restaking
Browse files Browse the repository at this point in the history
  • Loading branch information
drewstone committed Feb 1, 2025
1 parent b363388 commit 06f835b
Show file tree
Hide file tree
Showing 11 changed files with 1,214 additions and 382 deletions.
974 changes: 734 additions & 240 deletions pallets/multi-asset-delegation/src/functions/delegate.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pallets/multi-asset-delegation/src/functions/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ impl<T: Config> Pallet<T> {
let metadata = maybe_metadata.as_mut().ok_or(Error::<T>::NotDelegator)?;

// Ensure there are outstanding withdraw requests
ensure!(!metadata.withdraw_requests.is_empty(), Error::<T>::NowithdrawRequests);
ensure!(!metadata.withdraw_requests.is_empty(), Error::<T>::NoWithdrawRequests);

let current_round = Self::current_round();
let delay = T::LeaveDelegatorsDelay::get();
Expand Down
204 changes: 103 additions & 101 deletions pallets/multi-asset-delegation/src/functions/slash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,114 +15,116 @@
// along with Tangle. If not, see <http://www.gnu.org/licenses/>.

use crate::{types::*, Config, Delegators, Error, Event, Operators, Pallet};
use frame_support::{
ensure,
traits::{
fungibles::Mutate, tokens::Preservation, Currency, ExistenceRequirement, Get,
ReservableCurrency,
},
};
use sp_runtime::{traits::CheckedSub, DispatchError, Percent};
use tangle_primitives::{
services::{Asset, EvmAddressMapping},
BlueprintId, InstanceId,
};
use frame_support::{dispatch::DispatchResult, ensure, traits::Get, weights::Weight};
use sp_runtime::{traits::CheckedSub, DispatchError};
use tangle_primitives::{services::UnappliedSlash, traits::SlashManager};

impl<T: Config> Pallet<T> {
pub fn slash_operator(
operator: &T::AccountId,
blueprint_id: BlueprintId,
service_id: InstanceId,
percentage: Percent,
) -> Result<(), DispatchError> {
Operators::<T>::try_mutate(operator, |maybe_operator| {
let operator_data = maybe_operator.as_mut().ok_or(Error::<T>::NotAnOperator)?;
ensure!(operator_data.status == OperatorStatus::Active, Error::<T>::NotActiveOperator);

// Slash operator stake
let amount = percentage.mul_floor(operator_data.stake);
operator_data.stake = operator_data
.stake
.checked_sub(&amount)
.ok_or(Error::<T>::InsufficientStakeRemaining)?;

// Slash each delegator
for delegator in operator_data.delegations.iter() {
// Ignore errors from individual delegator slashing
let _ =
Self::slash_delegator(&delegator.delegator, operator, blueprint_id, percentage);
}

// transfer the slashed amount to the treasury
T::Currency::unreserve(operator, amount);
let _ = T::Currency::transfer(
operator,
&T::SlashedAmountRecipient::get(),
amount,
ExistenceRequirement::AllowDeath,
);

// emit event
Self::deposit_event(Event::OperatorSlashed { who: operator.clone(), amount });

Ok(())
})
/// Helper function to update operator storage for a slash
pub(crate) fn do_slash_operator(
unapplied_slash: &UnappliedSlash<T::AccountId, BalanceOf<T>, T::AssetId>,
) -> Result<Weight, DispatchError> {
let mut weight = T::DbWeight::get().reads(1);

Operators::<T>::try_mutate(
&unapplied_slash.operator,
|maybe_operator| -> DispatchResult {
let operator_data = maybe_operator.as_mut().ok_or(Error::<T>::NotAnOperator)?;
ensure!(
operator_data.status == OperatorStatus::Active,
Error::<T>::NotActiveOperator
);

// Update operator's stake
operator_data.stake = operator_data
.stake
.checked_sub(&unapplied_slash.own)
.ok_or(Error::<T>::InsufficientStakeRemaining)?;

weight += T::DbWeight::get().writes(1);
Ok(())
},
)?;

// Emit event for operator slash
Self::deposit_event(Event::OperatorSlashed {
who: unapplied_slash.operator.clone(),
amount: unapplied_slash.own,
});

Ok(weight)
}

/// Slashes a delegator's stake.
/// Helper function to update delegator storage for a slash
pub(crate) fn do_slash_delegator(
unapplied_slash: &UnappliedSlash<T::AccountId, BalanceOf<T>, T::AssetId>,
delegator: &T::AccountId,
) -> Result<Weight, DispatchError> {
let mut weight = T::DbWeight::get().reads(1);

let slash_amount = Delegators::<T>::try_mutate(
delegator,
|maybe_metadata| -> Result<BalanceOf<T>, DispatchError> {
let metadata = maybe_metadata.as_mut().ok_or(Error::<T>::NotDelegator)?;

// Find the delegation to the slashed operator
let delegation = metadata
.delegations
.iter_mut()
.find(|d| &d.operator == &unapplied_slash.operator)
.ok_or(Error::<T>::NoActiveDelegation)?;

// Find the slash amount for this delegator from the unapplied slash
let slash_amount = unapplied_slash
.others
.iter()
.find(|(d, _, _)| d == delegator)
.map(|(_, _, amount)| *amount)
.ok_or(Error::<T>::NoActiveDelegation)?;

// Update delegator's stake
delegation.amount = delegation
.amount
.checked_sub(&slash_amount)
.ok_or(Error::<T>::InsufficientStakeRemaining)?;

weight += T::DbWeight::get().writes(1);
Ok(slash_amount)
},
)?;

// Emit event for delegator slash
Self::deposit_event(Event::DelegatorSlashed {
who: delegator.clone(),
amount: slash_amount,
});

Ok(weight)
}
}

impl<T: Config> SlashManager<T::AccountId, BalanceOf<T>, T::AssetId> for Pallet<T> {
/// Updates operator storage to reflect a slash.
/// This only updates the storage items and does not handle asset transfers.
///
/// # Arguments
/// * `unapplied_slash` - The unapplied slash record containing slash details
fn slash_operator(
unapplied_slash: &UnappliedSlash<T::AccountId, BalanceOf<T>, T::AssetId>,
) -> Result<Weight, DispatchError> {
Self::do_slash_operator(unapplied_slash)
}

/// Updates delegator storage to reflect a slash.
/// This only updates the storage items and does not handle asset transfers.
///
/// * `delegator` - The account ID of the delegator.
/// * `operator` - The account ID of the operator.
/// * `blueprint_id` - The ID of the blueprint.
/// * `percentage` - The percentage of the stake to slash.
///
/// # Errors
///
/// Returns an error if the delegator is not found, or if the delegation is not active.
pub fn slash_delegator(
/// # Arguments
/// * `unapplied_slash` - The unapplied slash record containing slash details
/// * `delegator` - The account of the delegator being slashed
fn slash_delegator(
unapplied_slash: &UnappliedSlash<T::AccountId, BalanceOf<T>, T::AssetId>,
delegator: &T::AccountId,
operator: &T::AccountId,
blueprint_id: BlueprintId,
percentage: Percent,
) -> Result<(), DispatchError> {
Delegators::<T>::try_mutate(delegator, |maybe_metadata| {
let metadata = maybe_metadata.as_mut().ok_or(Error::<T>::NotDelegator)?;

let delegation = metadata
.delegations
.iter_mut()
.find(|d| &d.operator == operator)
.ok_or(Error::<T>::NoActiveDelegation)?;

// Sanity check the delegation type and blueprint_id. This shouldn't
// ever fail, since `slash_delegator` is called from the service module,
// which checks the blueprint_id before calling this function,
match &delegation.blueprint_selection {
DelegatorBlueprintSelection::Fixed(blueprints) => {
// For fixed delegation, ensure the blueprint_id is in the list
ensure!(blueprints.contains(&blueprint_id), Error::<T>::BlueprintNotSelected);
},
DelegatorBlueprintSelection::All => {
// For "All" type, no need to check blueprint_id
},
}

// Calculate and apply slash
let slash_amount = percentage.mul_floor(delegation.amount);
delegation.amount = delegation
.amount
.checked_sub(&slash_amount)
.ok_or(Error::<T>::InsufficientStakeRemaining)?;

// emit event
Self::deposit_event(Event::DelegatorSlashed {
who: delegator.clone(),
amount: slash_amount,
});

Ok(())
})
) -> Result<Weight, DispatchError> {
Self::do_slash_delegator(unapplied_slash, delegator)
}
}
Loading

0 comments on commit 06f835b

Please sign in to comment.