Skip to content

Commit

Permalink
Merge pull request #1925 from dusk-network/hard_slash
Browse files Browse the repository at this point in the history
Implement new hard slashing
  • Loading branch information
fed-franz authored Jul 5, 2024
2 parents be6d9d5 + 8c6ba86 commit 3763f32
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 28 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
69 changes: 47 additions & 22 deletions contracts/stake/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,9 @@ impl StakeState {
self.check_new_block();

let stake = self.load_or_create_stake_mut(stake_pk);
// Reset faults counter
// Reset faults counters
stake.faults = 0;
stake.hard_faults = 0;
stake.increase_reward(value);
rusk_abi::emit(
"reward",
Expand Down Expand Up @@ -355,43 +356,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
56 changes: 52 additions & 4 deletions contracts/stake/tests/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ fn reward_slash() -> Result<(), Error> {
amount: Some((stake_amount, 0)),
counter: 0,
faults: 0,
hard_faults: 0,
};

session.call::<_, ()>(
Expand Down Expand Up @@ -120,21 +121,24 @@ fn stake_hard_slash() -> Result<(), Error> {

let mut session = instantiate(rng, vm, &pk, GENESIS_VALUE);

let balance = dusk(14.0);
let stake_amount = dusk(100.0);
let hard_slash_amount = dusk(5.0);
let severity = 2;
let reward_amount = dusk(10.0);
let block_height = 0;

let stake_data = StakeData {
reward: 0,
amount: Some((balance, block_height)),
amount: Some((stake_amount, block_height)),
counter: 0,
faults: 0,
hard_faults: 0,
};

session.call::<_, ()>(
TRANSFER_CONTRACT,
"add_contract_balance",
&(STAKE_CONTRACT, balance),
&(STAKE_CONTRACT, stake_amount),
u64::MAX,
)?;

Expand All @@ -145,13 +149,57 @@ fn stake_hard_slash() -> Result<(), Error> {
u64::MAX,
)?;

let mut cur_balance = stake_amount;
// Simple hard fault (slash 10%)
let receipt = session.call::<_, ()>(
STAKE_CONTRACT,
"hard_slash",
&(stake_pk, None::<u64>, None::<u8>),
u64::MAX,
)?;
let expected_slash = stake_amount / 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);
cur_balance -= expected_slash;

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

let receipt = session.call::<_, ()>(
STAKE_CONTRACT,
"reward",
&(stake_pk, reward_amount),
u64::MAX,
)?;
assert_event(&receipt.events, "reward", &stake_pk, reward_amount);

// Simple hard fault post-reward (slash 10%)
// Rewards should reset 'hard_faults'
let receipt = session.call::<_, ()>(
STAKE_CONTRACT,
"hard_slash",
&(stake_pk, None::<u64>, None::<u8>),
u64::MAX,
)?;
let expected_slash = cur_balance / 100 * 10;
assert_event(&receipt.events, "hard_slash", &stake_pk, expected_slash);

Ok(())
}
3 changes: 3 additions & 0 deletions execution-core/src/stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ pub struct StakeData {
pub counter: u64,
/// Faults
pub faults: u8,
/// Hard Faults
pub hard_faults: u8,
}

impl StakeData {
Expand Down Expand Up @@ -199,6 +201,7 @@ impl StakeData {
reward,
counter: 0,
faults: 0,
hard_faults: 0,
}
}

Expand Down
1 change: 1 addition & 0 deletions rusk-recovery/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ fn generate_stake_state(
reward: staker.reward.unwrap_or_default(),
counter: 0,
faults: 0,
hard_faults: 0,
};
session
.call::<_, ()>(
Expand Down

0 comments on commit 3763f32

Please sign in to comment.