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

support multiple slash ratios #164

Merged
merged 5 commits into from
Nov 14, 2023
Merged
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
46 changes: 31 additions & 15 deletions contracts/provider/external-staking/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use crate::msg::{
StakeInfo, StakesResponse, TxResponse, ValidatorPendingRewards,
};
use crate::stakes::Stakes;
use crate::state::{Config, Distribution, Stake};
use crate::state::{Config, Distribution, SlashRatio, Stake};

pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
Expand Down Expand Up @@ -60,6 +60,11 @@ impl Default for ExternalStakingContract<'_> {
}
}

pub(crate) enum SlashingReason {
Offline,
DoubleSign,
}

#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
#[contract]
#[error(ContractError)]
Expand Down Expand Up @@ -95,21 +100,21 @@ impl ExternalStakingContract<'_> {
vault: String,
unbonding_period: u64,
remote_contact: crate::msg::AuthorizedEndpoint,
max_slashing: Decimal,
slash_ratio: SlashRatio,
) -> Result<Response, ContractError> {
let vault = ctx.deps.api.addr_validate(&vault)?;
let vault = VaultApiHelper(vault);

if max_slashing > Decimal::one() {
return Err(ContractError::InvalidMaxSlashing);
if slash_ratio.double_sign > Decimal::one() || slash_ratio.offline > Decimal::one() {
return Err(ContractError::InvalidSlashRatio);
}

let config = Config {
denom,
rewards_denom,
vault,
unbonding_period,
max_slashing,
slash_ratio,
};

self.config.save(ctx.deps.storage, &config)?;
Expand Down Expand Up @@ -472,7 +477,8 @@ impl ExternalStakingContract<'_> {
.tombstone_validator(deps.storage, valoper, height, time)?;
if active {
// Slash the validator (if bonded)
let slash_msg = self.handle_slashing(&env, deps.storage, valoper)?;
let slash_msg =
self.handle_slashing(&env, deps.storage, valoper, SlashingReason::DoubleSign)?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼

if let Some(msg) = slash_msg {
msgs.push(msg)
}
Expand Down Expand Up @@ -506,7 +512,8 @@ impl ExternalStakingContract<'_> {
if active {
// Slash the validator, if bonded
// TODO: Slash with a different slash ratio! (downtime / offline slash ratio)
let slash_msg = self.handle_slashing(&env, deps.storage, valoper)?;
let slash_msg =
self.handle_slashing(&env, deps.storage, valoper, SlashingReason::Offline)?;
if let Some(msg) = slash_msg {
msgs.push(msg)
}
Expand Down Expand Up @@ -845,8 +852,13 @@ impl ExternalStakingContract<'_> {
env: &Env,
storage: &mut dyn Storage,
validator: &str,
reason: SlashingReason,
) -> Result<Option<WasmMsg>, ContractError> {
let config = self.config.load(storage)?;
let slash_ratio = match reason {
SlashingReason::Offline => config.slash_ratio.offline,
SlashingReason::DoubleSign => config.slash_ratio.double_sign,
};
// Get the list of users staking via this validator
let users = self
.stakes
Expand All @@ -872,7 +884,7 @@ impl ExternalStakingContract<'_> {
// Calculating slashing with always the `high` value of the range goes against the user
// in some scenario (pending stakes while slashing); but the scenario is relatively
// unlikely.
let stake_slash = stake_high * config.max_slashing;
let stake_slash = stake_high * slash_ratio;
// Requires proper saturating methods in commit/rollback_stake/unstake
stake.stake = ValueRange::new(
stake_low.saturating_sub(stake_slash),
Expand All @@ -891,7 +903,7 @@ impl ExternalStakingContract<'_> {
self.distribution.save(storage, validator, &distribution)?;

// Slash the unbondings
let pending_slashed = stake.slash_pending(&env.block, config.max_slashing);
let pending_slashed = stake.slash_pending(&env.block, slash_ratio);

self.stakes.stake.save(storage, (&user, validator), stake)?;

Expand Down Expand Up @@ -1161,7 +1173,7 @@ pub mod cross_staking {

use super::*;
use cosmwasm_std::{from_binary, Binary};
use mesh_apis::{cross_staking_api::CrossStakingApi, local_staking_api::MaxSlashResponse};
use mesh_apis::{cross_staking_api::CrossStakingApi, local_staking_api::SlashRatioResponse};

#[contract(module=crate::contract)]
#[messages(mesh_apis::cross_staking_api as CrossStakingApi)]
Expand Down Expand Up @@ -1387,10 +1399,11 @@ pub mod cross_staking {
}

#[msg(query)]
fn max_slash(&self, ctx: QueryCtx) -> Result<MaxSlashResponse, ContractError> {
let Config { max_slashing, .. } = self.config.load(ctx.deps.storage)?;
Ok(MaxSlashResponse {
max_slash: max_slashing,
fn max_slash(&self, ctx: QueryCtx) -> Result<SlashRatioResponse, ContractError> {
let Config { slash_ratio, .. } = self.config.load(ctx.deps.storage)?;
Ok(SlashRatioResponse {
slash_ratio_dsign: slash_ratio.double_sign,
slash_ratio_offline: slash_ratio.offline,
})
}
}
Expand Down Expand Up @@ -1430,7 +1443,10 @@ mod tests {
connection_id: "connection_id_1".to_string(),
port_id: "port_id_1".to_string(),
},
Decimal::percent(10),
SlashRatio {
double_sign: Decimal::percent(10),
offline: Decimal::percent(10),
},
)
.unwrap();
let exec_ctx = ExecCtx {
Expand Down
4 changes: 2 additions & 2 deletions contracts/provider/external-staking/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ pub enum ContractError {
#[error("Invalid denom, {0} expected")]
InvalidDenom(String),

#[error("You cannot use a max slashing rate over 1.0 (100%)")]
InvalidMaxSlashing,
#[error("You cannot specify a slash ratio over 1.0 (100%)")]
InvalidSlashRatio,

#[error("Not enough tokens staked, up to {0} can be unbond")]
NotEnoughStake(Uint128),
Expand Down
18 changes: 13 additions & 5 deletions contracts/provider/external-staking/src/multitest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::contract::cross_staking::test_utils::CrossStakingApi;
use crate::contract::multitest_utils::{CodeId, ExternalStakingContractProxy};
use crate::error::ContractError;
use crate::msg::{AuthorizedEndpoint, ReceiveVirtualStake, StakeInfo, ValidatorPendingRewards};
use crate::state::Stake;
use crate::state::{SlashRatio, Stake};
use crate::test_methods_impl::test_utils::TestMethods;
use utils::{
assert_rewards, get_last_external_staking_pending_tx_id, AppExt as _, ContractExt as _,
Expand All @@ -31,7 +31,8 @@ const STAR: &str = "star";
/// 10% slashing on the remote chain
const SLASHING_PERCENTAGE: u64 = 10;
/// 5% slashing on the local chain (so we can differentiate in future tests)
const LOCAL_SLASHING_PERCENTAGE: u64 = 5;
const LOCAL_SLASHING_PERCENTAGE_DSIGN: u64 = 5;
const LOCAL_SLASHING_PERCENTAGE_OFFLINE: u64 = 5;

// Shortcut setuping all needed contracts
//
Expand All @@ -52,7 +53,8 @@ fn setup<'app>(
let native_staking_instantiate = NativeStakingInstantiateMsg {
denom: OSMO.to_owned(),
proxy_code_id: native_staking_proxy_code.code_id(),
max_slashing: Decimal::percent(LOCAL_SLASHING_PERCENTAGE),
slash_ratio_dsign: Decimal::percent(LOCAL_SLASHING_PERCENTAGE_DSIGN),
slash_ratio_offline: Decimal::percent(LOCAL_SLASHING_PERCENTAGE_OFFLINE),
};

let staking_init = StakingInitInfo {
Expand All @@ -75,7 +77,10 @@ fn setup<'app>(
vault.contract_addr.to_string(),
unbond_period,
remote_contact,
Decimal::percent(SLASHING_PERCENTAGE),
SlashRatio {
double_sign: Decimal::percent(SLASHING_PERCENTAGE),
offline: Decimal::percent(SLASHING_PERCENTAGE),
},
)
.call(owner)?;

Expand All @@ -95,7 +100,10 @@ fn instantiate() {
assert_eq!(stakes.stakes, []);

let max_slash = contract.cross_staking_api_proxy().max_slash().unwrap();
assert_eq!(max_slash.max_slash, Decimal::percent(SLASHING_PERCENTAGE));
assert_eq!(
max_slash.slash_ratio_dsign,
Decimal::percent(SLASHING_PERCENTAGE)
);
}

#[test]
Expand Down
10 changes: 8 additions & 2 deletions contracts/provider/external-staking/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ pub struct Config {
pub vault: VaultApiHelper,
/// Unbonding period for claims in seconds
pub unbonding_period: u64,
/// Max slash percentage (from InstantiateMsg, maybe later from the chain)
pub max_slashing: Decimal,
/// The slash ratio
pub slash_ratio: SlashRatio,
}

#[cw_serde]
pub struct SlashRatio {
pub double_sign: Decimal,
pub offline: Decimal,
}

/// All single stake related information - entry per `(user, validator)` pair, including
Expand Down
7 changes: 6 additions & 1 deletion contracts/provider/external-staking/src/test_methods_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,12 @@ impl TestMethods for ExternalStakingContract<'_> {
) -> Result<Response, ContractError> {
#[cfg(any(test, feature = "mt"))]
{
let slash_msg = self.handle_slashing(&ctx.env, ctx.deps.storage, &validator)?;
let slash_msg = self.handle_slashing(
&ctx.env,
ctx.deps.storage,
&validator,
crate::contract::SlashingReason::DoubleSign,
)?;
match slash_msg {
Some(msg) => Ok(Response::new().add_message(msg)),
None => Ok(Response::new()),
Expand Down
3 changes: 2 additions & 1 deletion contracts/provider/native-staking-proxy/src/multitest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ fn setup<'app>(
msg: to_binary(&mesh_native_staking::contract::InstantiateMsg {
denom: OSMO.to_owned(),
proxy_code_id: staking_proxy_code.code_id(),
max_slashing: Decimal::percent(5),
slash_ratio_dsign: Decimal::percent(5),
slash_ratio_offline: Decimal::percent(5),
})
.unwrap(),
label: None,
Expand Down
10 changes: 6 additions & 4 deletions contracts/provider/native-staking/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,19 @@ impl NativeStakingContract<'_> {
ctx: InstantiateCtx,
denom: String,
proxy_code_id: u64,
max_slashing: Decimal,
slash_ratio_dsign: Decimal,
slash_ratio_offline: Decimal,
) -> Result<Response, ContractError> {
if max_slashing > Decimal::one() {
return Err(ContractError::InvalidMaxSlashing);
if slash_ratio_dsign > Decimal::one() || slash_ratio_offline > Decimal::one() {
return Err(ContractError::InvalidSlashRatio);
}

let config = Config {
denom,
proxy_code_id,
vault: ctx.info.sender,
max_slashing,
slash_ratio_dsign,
slash_ratio_offline,
};
self.config.save(ctx.deps.storage, &config)?;
set_contract_version(ctx.deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
Expand Down
4 changes: 2 additions & 2 deletions contracts/provider/native-staking/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ pub enum ContractError {
#[error("Missing proxy contract for {0}")]
NoProxy(String),

#[error("You cannot use a max slashing rate over 1.0 (100%)")]
InvalidMaxSlashing,
#[error("You cannot specify a slash ratio over 1.0 (100%)")]
InvalidSlashRatio,
}
15 changes: 10 additions & 5 deletions contracts/provider/native-staking/src/local_staking_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use sylvia::types::QueryCtx;
use sylvia::{contract, types::ExecCtx};

#[allow(unused_imports)]
use mesh_apis::local_staking_api::{self, LocalStakingApi, MaxSlashResponse};
use mesh_apis::local_staking_api::{self, LocalStakingApi, SlashRatioResponse};

use crate::contract::{NativeStakingContract, REPLY_ID_INSTANTIATE};
use crate::error::ContractError;
Expand Down Expand Up @@ -121,10 +121,15 @@ impl LocalStakingApi for NativeStakingContract<'_> {

/// Returns the maximum percentage that can be slashed
#[msg(query)]
fn max_slash(&self, ctx: QueryCtx) -> Result<MaxSlashResponse, Self::Error> {
let Config { max_slashing, .. } = self.config.load(ctx.deps.storage)?;
Ok(MaxSlashResponse {
max_slash: max_slashing,
fn max_slash(&self, ctx: QueryCtx) -> Result<SlashRatioResponse, Self::Error> {
let Config {
slash_ratio_dsign,
slash_ratio_offline,
..
} = self.config.load(ctx.deps.storage)?;
Ok(SlashRatioResponse {
slash_ratio_dsign,
slash_ratio_offline,
})
}
}
22 changes: 15 additions & 7 deletions contracts/provider/native-staking/src/multitest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ use crate::msg::{OwnerByProxyResponse, ProxyByOwnerResponse};

const OSMO: &str = "OSMO";

const SLASHING_PERCENTAGE: u64 = 15;
const SLASHING_PERCENTAGE_DSIGN: u64 = 15;
const SLASHING_PERCENTAGE_OFFLINE: u64 = 10;

fn slashing_rate() -> Decimal {
Decimal::percent(SLASHING_PERCENTAGE)
fn slashing_rate_dsign() -> Decimal {
Decimal::percent(SLASHING_PERCENTAGE_DSIGN)
}

fn slashing_rate_offline() -> Decimal {
Decimal::percent(SLASHING_PERCENTAGE_OFFLINE)
}

fn app(balances: &[(&str, (u128, &str))], validators: &[&str]) -> App<MtApp> {
Expand Down Expand Up @@ -108,7 +113,8 @@ fn instantiation() {
.instantiate(
OSMO.to_owned(),
staking_proxy_code.code_id(),
slashing_rate(),
slashing_rate_dsign(),
slashing_rate_offline(),
)
.with_label("Staking")
.call(owner)
Expand All @@ -118,7 +124,7 @@ fn instantiation() {
assert_eq!(config.denom, OSMO);

let res = staking.local_staking_api_proxy().max_slash().unwrap();
assert_eq!(res.max_slash, slashing_rate());
assert_eq!(res.slash_ratio_dsign, slashing_rate_dsign());
}

#[test]
Expand All @@ -141,7 +147,8 @@ fn receiving_stake() {
.instantiate(
OSMO.to_owned(),
staking_proxy_code.code_id(),
slashing_rate(),
slashing_rate_dsign(),
slashing_rate_offline(),
)
.with_label("Staking")
.call(owner)
Expand Down Expand Up @@ -260,7 +267,8 @@ fn releasing_proxy_stake() {
msg: to_binary(&crate::contract::InstantiateMsg {
denom: OSMO.to_owned(),
proxy_code_id: staking_proxy_code.code_id(),
max_slashing: slashing_rate(),
slash_ratio_dsign: slashing_rate_dsign(),
slash_ratio_offline: slashing_rate_offline(),
})
.unwrap(),
label: None,
Expand Down
7 changes: 5 additions & 2 deletions contracts/provider/native-staking/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub struct Config {
/// The address of the vault contract (where we get and return stake)
pub vault: Addr,

/// Max slash percentage (from InstantiateMsg, maybe later from the chain)
pub max_slashing: Decimal,
/// The slash ratio for double signing
pub slash_ratio_dsign: Decimal,

/// The slash ratio for being offline
pub slash_ratio_offline: Decimal,
}
Loading
Loading