Skip to content

Commit

Permalink
contracts: change 'hard slash' mechanism
Browse files Browse the repository at this point in the history
Change the code to slash the stake incrementally, depending on
the number of 'hard_faults'. The stake is slashed by 10% times
the number of faults. The 'severity' parameter can be used to
increase 'hard_faults' by more than 1.

The slashed stake is also suspended for the resto of the current
epoch plus 'hard_faults'additional epochs.

See also #1651
  • Loading branch information
fed-franz committed Jul 3, 2024
1 parent 5aad642 commit 40548d0
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 24 deletions.
4 changes: 2 additions & 2 deletions contracts/stake/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ unsafe fn slash(arg_len: u32) -> u32 {

#[no_mangle]
unsafe fn hard_slash(arg_len: u32) -> u32 {
rusk_abi::wrap_call(arg_len, |(pk, value)| {
rusk_abi::wrap_call(arg_len, |(pk, value, severity)| {
assert_external_caller();
STATE.hard_slash(&pk, value);
STATE.hard_slash(&pk, value, severity);
})
}

Expand Down
66 changes: 45 additions & 21 deletions contracts/stake/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,43 +355,67 @@ impl StakeState {
///
/// If the stake is less than the `to_slash` amount, then the stake is
/// depleted
pub fn hard_slash(&mut self, stake_pk: &StakePublicKey, to_slash: u64) {
pub fn hard_slash(
&mut self,
stake_pk: &StakePublicKey,
to_slash: Option<u64>,
severity: Option<u8>,
) {
self.check_new_block();

let stake_info = self
let stake = self
.get_stake_mut(stake_pk)
.expect("The stake to slash should exist");

let prev_value = Some(stake_info.clone());

let stake = stake_info.amount.as_mut();
// This can happen if the provisioner unstake in the same block
if stake.is_none() {
return;
}

let stake = stake.expect("The stake amount to slash should exist");

let to_slash = min(to_slash, stake.0);
if to_slash == 0 {
// Stake can have no amount if provisioner unstake in the same block
if stake.amount().is_none() {
return;
}

// Update the staked amount
stake.0 -= to_slash;
let prev_value = Some(stake.clone());

Self::deduct_contract_balance(to_slash);
let (stake_amount, eligibility) =
stake.amount.as_mut().expect("stake_to_exists");

// Update the total burnt amount
self.burnt_amount += to_slash;
let severity = severity.unwrap_or(1);
stake.hard_faults = stake.hard_faults.saturating_add(severity);
let hard_faults = stake.hard_faults as u64;

// The stake is shifted (aka suspended) for the rest of the current
// epoch plus hard_faults epochs
let to_shift = hard_faults * EPOCH;
*eligibility = next_epoch(rusk_abi::block_height()) + to_shift;
rusk_abi::emit(
"hard_slash",
"suspended",
StakingEvent {
public_key: *stake_pk,
value: to_slash,
value: *eligibility,
},
);

// Slash the provided amount or calculate the percentage according to
// hard faults
let to_slash =
to_slash.unwrap_or(*stake_amount / 100 * hard_faults * 10);
let to_slash = min(to_slash, *stake_amount);

if to_slash > 0 {
// Update the staked amount
*stake_amount -= to_slash;
Self::deduct_contract_balance(to_slash);

// Update the total burnt amount
self.burnt_amount += to_slash;

rusk_abi::emit(
"hard_slash",
StakingEvent {
public_key: *stake_pk,
value: to_slash,
},
);
}

let key = stake_pk.to_bytes();
self.previous_block_state
.entry(key)
Expand Down
26 changes: 25 additions & 1 deletion contracts/stake/tests/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ fn stake_hard_slash() -> Result<(), Error> {

let balance = dusk(14.0);
let hard_slash_amount = dusk(5.0);
let severity = 2;
let block_height = 0;

let stake_data = StakeData {
Expand All @@ -147,10 +148,33 @@ fn stake_hard_slash() -> Result<(), Error> {
u64::MAX,
)?;

let mut cur_balance = balance;
// Simple hard fault (slash 10%)
let receipt = session.call::<_, ()>(
STAKE_CONTRACT,
"hard_slash",
&(stake_pk, hard_slash_amount),
&(stake_pk, None::<u64>, None::<u8>),
u64::MAX,
)?;
let expected_slash = balance / 100 * 10;
assert_event(&receipt.events, "hard_slash", &stake_pk, expected_slash);
cur_balance -= expected_slash;

// Severe hard fault (slash 30%)
let receipt = session.call::<_, ()>(
STAKE_CONTRACT,
"hard_slash",
&(stake_pk, None::<u64>, Some(severity as u8)),
u64::MAX,
)?;
let expected_slash = cur_balance / 100 * (1 + severity) * 10;
assert_event(&receipt.events, "hard_slash", &stake_pk, expected_slash);

// Direct slash (slash hard_slash_amount)
let receipt = session.call::<_, ()>(
STAKE_CONTRACT,
"hard_slash",
&(stake_pk, Some(hard_slash_amount), None::<u8>),
u64::MAX,
)?;
assert_event(&receipt.events, "hard_slash", &stake_pk, hard_slash_amount);
Expand Down

0 comments on commit 40548d0

Please sign in to comment.