Skip to content

Commit

Permalink
Merge pull request #3233 from dusk-network/stake-conf
Browse files Browse the repository at this point in the history
  • Loading branch information
herr-seppia authored Dec 20, 2024
2 parents 49f9d63 + bd07490 commit 09def4b
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 47 deletions.
4 changes: 2 additions & 2 deletions consensus/src/user/provisioners.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::collections::BTreeMap;
use std::mem;

use dusk_core::dusk;
use dusk_core::stake::MINIMUM_STAKE;
use dusk_core::stake::DEFAULT_MINIMUM_STAKE;
use node_data::bls::{PublicKey, PublicKeyBytes};
use node_data::ledger::Seed;
use node_data::StepName;
Expand Down Expand Up @@ -182,7 +182,7 @@ impl Provisioners {
round: u64,
) -> impl Iterator<Item = (&PublicKey, &Stake)> {
self.members.iter().filter(move |(_, m)| {
m.is_eligible(round) && m.value() >= MINIMUM_STAKE
m.is_eligible(round) && m.value() >= DEFAULT_MINIMUM_STAKE
})
}

Expand Down
13 changes: 13 additions & 0 deletions contracts/stake/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ unsafe fn get_version(arg_len: u32) -> u32 {
rusk_abi::wrap_call(arg_len, |_: ()| STATE.get_version())
}

#[no_mangle]
unsafe fn get_config(arg_len: u32) -> u32 {
rusk_abi::wrap_call(arg_len, |_: ()| STATE.config().clone())
}

// "Feeder" queries

#[no_mangle]
Expand All @@ -116,6 +121,14 @@ unsafe fn before_state_transition(arg_len: u32) -> u32 {
})
}

#[no_mangle]
unsafe fn set_config(arg_len: u32) -> u32 {
rusk_abi::wrap_call(arg_len, |config| {
assert_external_caller();
STATE.configure(config)
})
}

#[no_mangle]
unsafe fn insert_stake(arg_len: u32) -> u32 {
rusk_abi::wrap_call(arg_len, |(pk, stake_data)| {
Expand Down
47 changes: 28 additions & 19 deletions contracts/stake/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,19 @@

use alloc::collections::BTreeMap;
use alloc::vec::Vec;

use core::cmp::min;

use dusk_bytes::Serializable;

use dusk_core::{
signatures::bls::PublicKey as BlsPublicKey,
stake::{
next_epoch, Reward, SlashEvent, Stake, StakeAmount, StakeData,
StakeEvent, StakeFundOwner, StakeKeys, Withdraw, WithdrawToContract,
EPOCH, MINIMUM_STAKE, STAKE_CONTRACT, STAKE_WARNINGS,
},
transfer::{ContractToContract, ReceiveFromContract, TRANSFER_CONTRACT},
ContractId,
use dusk_core::signatures::bls::PublicKey as BlsPublicKey;
use dusk_core::stake::{
next_epoch, Reward, SlashEvent, Stake, StakeAmount, StakeConfig, StakeData,
StakeEvent, StakeFundOwner, StakeKeys, Withdraw, WithdrawToContract, EPOCH,
STAKE_CONTRACT,
};

use crate::*;
use dusk_core::transfer::{
ContractToContract, ReceiveFromContract, TRANSFER_CONTRACT,
};
use dusk_core::ContractId;

/// Contract keeping track of each public key's stake.
///
Expand All @@ -34,23 +30,33 @@ use crate::*;
/// valid stake.
#[derive(Debug, Default, Clone)]
pub struct StakeState {
stakes: BTreeMap<[u8; BlsPublicKey::SIZE], (StakeData, StakeKeys)>,
burnt_amount: u64,
config: StakeConfig,
previous_block_state:
BTreeMap<[u8; BlsPublicKey::SIZE], (Option<StakeData>, BlsPublicKey)>,
stakes: BTreeMap<[u8; BlsPublicKey::SIZE], (StakeData, StakeKeys)>,
}

const STAKE_CONTRACT_VERSION: u64 = 8;

impl StakeState {
pub const fn new() -> Self {
Self {
stakes: BTreeMap::new(),
burnt_amount: 0u64,
config: StakeConfig::new(),
previous_block_state: BTreeMap::new(),
stakes: BTreeMap::new(),
}
}

pub fn config(&self) -> &StakeConfig {
&self.config
}

pub fn configure(&mut self, config: StakeConfig) {
self.config = config;
}

pub fn on_new_block(&mut self) {
self.previous_block_state.clear()
}
Expand Down Expand Up @@ -80,6 +86,7 @@ impl StakeState {
}

pub fn stake(&mut self, stake: Stake) {
let minimum_stake = self.config.minimum_stake;
let value = stake.value();
let signature = *stake.signature();

Expand All @@ -91,7 +98,7 @@ impl StakeState {
let prev_stake = self.get_stake(&stake.keys().account).copied();
let (loaded_stake, keys) = self.load_or_create_stake_mut(stake.keys());

if loaded_stake.amount.is_none() && value < MINIMUM_STAKE {
if loaded_stake.amount.is_none() && value < minimum_stake {
panic!("The staked value is lower than the minimum amount!");
}

Expand Down Expand Up @@ -143,6 +150,7 @@ impl StakeState {
let stake: Stake =
rkyv::from_bytes(&recv.data).expect("Invalid stake received");
let value = stake.value();
let minimum_stake = self.config.minimum_stake;

if stake.chain_id() != self.chain_id() {
panic!("The stake must target the correct chain");
Expand All @@ -157,7 +165,7 @@ impl StakeState {
assert!(value == recv.value, "Stake amount mismatch");

if loaded_stake.amount.is_none() {
if value < MINIMUM_STAKE {
if value < minimum_stake {
panic!("The staked value is lower than the minimum amount!");
}

Expand Down Expand Up @@ -254,7 +262,7 @@ impl StakeState {
if loaded_stake.reward == 0 {
self.stakes.remove(&unstake.account().to_bytes());
}
} else if stake.total_funds() < MINIMUM_STAKE {
} else if stake.total_funds() < self.config.minimum_stake {
panic!("Stake left is lower than minimum stake");
}

Expand Down Expand Up @@ -513,6 +521,7 @@ impl StakeState {
/// depleted and the provisioner eligibility is shifted to the
/// next epoch as well
pub fn slash(&mut self, account: &BlsPublicKey, to_slash: Option<u64>) {
let stake_warnings = self.config.warnings;
let (stake, _) = self
.get_stake_mut(account)
.expect("The stake to slash should exist");
Expand All @@ -525,7 +534,7 @@ impl StakeState {

stake.faults = stake.faults.saturating_add(1);
let effective_faults =
stake.faults.saturating_sub(STAKE_WARNINGS) as u64;
stake.faults.saturating_sub(stake_warnings) as u64;

let stake_amount = stake.amount.as_mut().expect("stake_to_exists");

Expand Down
44 changes: 39 additions & 5 deletions core/src/stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,45 @@ pub const STAKE_CONTRACT: ContractId = crate::reserved(0x2);
/// Epoch used for stake operations
pub const EPOCH: u64 = 2160;

/// Number of warnings before being penalized
pub const STAKE_WARNINGS: u8 = 1;
/// Default number of warnings before being penalized
pub const DEFAULT_STAKE_WARNINGS: u8 = 1;

/// The minimum amount of Dusk one can stake.
#[deprecated(
since = "0.1.0",
note = "please use `DEFAULT_MINIMUM_STAKE` instead"
)]
pub const MINIMUM_STAKE: Dusk = DEFAULT_MINIMUM_STAKE;

/// The default minimum amount of Dusk one can stake.
pub const DEFAULT_MINIMUM_STAKE: Dusk = dusk(1_000.0);

/// Configuration for the stake contract
#[derive(Debug, Clone, Archive, Serialize, Deserialize)]
#[archive_attr(derive(CheckBytes))]
pub struct StakeConfig {
/// Number of warnings before being penalized
pub warnings: u8,
/// Minimum amount of Dusk that can be staked
pub minimum_stake: Dusk,
}

impl StakeConfig {
/// Create a new default stake configuration.
#[must_use]
pub const fn new() -> Self {
Self {
warnings: DEFAULT_STAKE_WARNINGS,
minimum_stake: DEFAULT_MINIMUM_STAKE,
}
}
}

impl Default for StakeConfig {
fn default() -> Self {
Self::new()
}
}

/// Calculate the block height at which the next epoch takes effect.
#[must_use]
Expand Down Expand Up @@ -374,9 +411,6 @@ pub struct SlashEvent {
pub next_eligibility: u64,
}

/// The minimum amount of Dusk one can stake.
pub const MINIMUM_STAKE: Dusk = dusk(1_000.0);

/// The representation of a public key's stake.
///
/// A user can stake for a particular `amount` larger in value than the
Expand Down
5 changes: 3 additions & 2 deletions rusk-recovery/src/state/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ mod tests {

use std::error::Error;

use dusk_core::stake::MINIMUM_STAKE;
use dusk_core::stake::DEFAULT_MINIMUM_STAKE;

use super::*;
use crate::state;
Expand Down Expand Up @@ -133,7 +133,8 @@ mod tests {
}

if !testnet.stakes().any(|s| {
s.amount >= MINIMUM_STAKE && s.eligibility.unwrap_or_default() == 0
s.amount >= DEFAULT_MINIMUM_STAKE
&& s.eligibility.unwrap_or_default() == 0
}) {
panic!("Testnet must have at least a provisioner configured");
}
Expand Down
4 changes: 2 additions & 2 deletions rusk-wallet/src/bin/io/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crossterm::{

use anyhow::Result;
use bip39::{ErrorKind, Language, Mnemonic};
use dusk_core::stake::MINIMUM_STAKE;
use dusk_core::stake::DEFAULT_MINIMUM_STAKE;

use inquire::ui::RenderConfig;
use inquire::validator::Validation;
Expand Down Expand Up @@ -267,7 +267,7 @@ pub(crate) fn request_optional_token_amt(

/// Request amount of tokens that can't be lower than MINIMUM_STAKE
pub(crate) fn request_stake_token_amt(balance: Dusk) -> Result<Dusk, Error> {
let min: Dusk = MINIMUM_STAKE.into();
let min: Dusk = DEFAULT_MINIMUM_STAKE.into();

request_token("stake", min, balance, None).map_err(Error::from)
}
Expand Down
27 changes: 15 additions & 12 deletions rusk/tests/services/contract_stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use std::path::Path;
use std::sync::{Arc, RwLock};

use dusk_core::stake::{self, Stake, EPOCH, MINIMUM_STAKE};
use dusk_core::stake::{self, Stake, DEFAULT_MINIMUM_STAKE, EPOCH};

use dusk_bytes::Serializable;
use dusk_core::transfer::data::ContractCall;
Expand Down Expand Up @@ -72,7 +72,7 @@ pub async fn stake_from_contract() -> Result<()> {
let stake = Stake::new_from_contract(
&sk,
contract_id,
MINIMUM_STAKE,
DEFAULT_MINIMUM_STAKE,
rusk.chain_id().unwrap(),
);
let call = ContractCall::new(contract_id, "stake", &stake)
Expand All @@ -81,7 +81,7 @@ pub async fn stake_from_contract() -> Result<()> {
.moonlight_execute(
0,
0,
MINIMUM_STAKE,
DEFAULT_MINIMUM_STAKE,
GAS_LIMIT,
GAS_PRICE,
Some(call.clone()),
Expand All @@ -92,15 +92,15 @@ pub async fn stake_from_contract() -> Result<()> {

let stake = wallet.get_stake(0).expect("stake to be found");
assert_eq!(
MINIMUM_STAKE,
DEFAULT_MINIMUM_STAKE,
stake.amount.expect("stake amount to be found").value
);

let stake_from_contract = wallet
.moonlight_execute(
0,
0,
MINIMUM_STAKE,
DEFAULT_MINIMUM_STAKE,
GAS_LIMIT,
GAS_PRICE,
Some(call.clone()),
Expand All @@ -111,15 +111,15 @@ pub async fn stake_from_contract() -> Result<()> {

let stake = wallet.get_stake(0).expect("stake to be found");
assert_eq!(
MINIMUM_STAKE * 2,
DEFAULT_MINIMUM_STAKE * 2,
stake.amount.expect("stake amount to be found").value
);

let stake_from_contract = wallet
.moonlight_execute(
0,
0,
MINIMUM_STAKE,
DEFAULT_MINIMUM_STAKE,
GAS_LIMIT,
GAS_PRICE,
Some(call),
Expand All @@ -136,12 +136,12 @@ pub async fn stake_from_contract() -> Result<()> {

let stake = wallet.get_stake(0).expect("stake to be found");
assert_eq!(
MINIMUM_STAKE * 2 + (MINIMUM_STAKE / 10 * 9) as u64,
DEFAULT_MINIMUM_STAKE * 2 + (DEFAULT_MINIMUM_STAKE / 10 * 9) as u64,
stake.amount.expect("stake amount to be found").value
);

assert_eq!(
(MINIMUM_STAKE / 10) as u64,
(DEFAULT_MINIMUM_STAKE / 10) as u64,
stake.amount.expect("stake amount to be found").locked
);

Expand Down Expand Up @@ -175,7 +175,7 @@ pub async fn stake_from_contract() -> Result<()> {
&mut rng,
&sk,
contract_id,
3 * MINIMUM_STAKE,
3 * DEFAULT_MINIMUM_STAKE,
transfer::withdraw::WithdrawReceiver::Moonlight(pk),
transfer::withdraw::WithdrawReplayToken::Moonlight(7),
),
Expand All @@ -189,7 +189,7 @@ pub async fn stake_from_contract() -> Result<()> {
.amount
.unwrap()
.total_funds(),
3 * MINIMUM_STAKE
3 * DEFAULT_MINIMUM_STAKE
);
let call = ContractCall::new(contract_id, "unstake", &unstake)
.expect("call to be successful");
Expand All @@ -202,7 +202,10 @@ pub async fn stake_from_contract() -> Result<()> {
assert_eq!(wallet.get_stake(0).expect("stake to exists").amount, None);
let new_balance = wallet.get_account(0).unwrap().balance;
let fee_paid = tx.gas_spent * GAS_PRICE;
assert_eq!(new_balance, prev_balance + 3 * MINIMUM_STAKE - fee_paid);
assert_eq!(
new_balance,
prev_balance + 3 * DEFAULT_MINIMUM_STAKE - fee_paid
);

let current_reward = wallet.get_stake(0).expect("Stake to exists").reward;

Expand Down
4 changes: 2 additions & 2 deletions rusk/tests/services/moonlight_stake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use std::path::Path;
use std::sync::{Arc, RwLock};

use dusk_core::stake::MINIMUM_STAKE;
use dusk_core::stake::DEFAULT_MINIMUM_STAKE;

use rand::prelude::*;
use rand::rngs::StdRng;
Expand Down Expand Up @@ -149,7 +149,7 @@ pub async fn stake() -> Result<()> {
info!("Original Root: {:?}", hex::encode(original_root));

// Perform some staking actions.
wallet_stake(&rusk, &wallet, MINIMUM_STAKE);
wallet_stake(&rusk, &wallet, DEFAULT_MINIMUM_STAKE);

// Check the state's root is changed from the original one
let new_root = rusk.state_root();
Expand Down
Loading

0 comments on commit 09def4b

Please sign in to comment.