diff --git a/contracts/provider/external-staking/src/contract.rs b/contracts/provider/external-staking/src/contract.rs index 77312aa2..c9bf2c07 100644 --- a/contracts/provider/external-staking/src/contract.rs +++ b/contracts/provider/external-staking/src/contract.rs @@ -80,6 +80,7 @@ impl ExternalStakingContract<'_> { Ok(id) } + #[allow(clippy::too_many_arguments)] #[msg(instantiate)] pub fn instantiate( &self, @@ -89,15 +90,21 @@ impl ExternalStakingContract<'_> { vault: String, unbonding_period: u64, remote_contact: crate::msg::AuthorizedEndpoint, + max_slashing: Decimal, ) -> Result { let vault = ctx.deps.api.addr_validate(&vault)?; let vault = VaultApiHelper(vault); + if max_slashing > Decimal::one() { + return Err(ContractError::InvalidMaxSlashing); + } + let config = Config { denom, rewards_denom, vault, unbonding_period, + max_slashing, }; self.config.save(ctx.deps.storage, &config)?; @@ -899,16 +906,11 @@ pub mod cross_staking { } #[msg(query)] - fn max_slash(&self, _ctx: QueryCtx) -> Result { - // TODO: Properly set this value - // Arbitrary value - only to make some testing possible - // - // Probably should be queried from remote chain - let resp = MaxSlashResponse { - max_slash: Decimal::percent(5), - }; - - Ok(resp) + fn max_slash(&self, ctx: QueryCtx) -> Result { + let Config { max_slashing, .. } = self.config.load(ctx.deps.storage)?; + Ok(MaxSlashResponse { + max_slash: max_slashing, + }) } } } diff --git a/contracts/provider/external-staking/src/error.rs b/contracts/provider/external-staking/src/error.rs index 7b8a7402..0b21e131 100644 --- a/contracts/provider/external-staking/src/error.rs +++ b/contracts/provider/external-staking/src/error.rs @@ -27,6 +27,9 @@ pub enum ContractError { #[error("Invalid denom, {0} expected")] InvalidDenom(String), + #[error("You cannot use a max slashing rate over 1.0 (100%)")] + InvalidMaxSlashing, + #[error("Not enough tokens staked, up to {0} can be unbond")] NotEnoughStake(Uint128), diff --git a/contracts/provider/external-staking/src/multitest.rs b/contracts/provider/external-staking/src/multitest.rs index 06d5afed..76837a86 100644 --- a/contracts/provider/external-staking/src/multitest.rs +++ b/contracts/provider/external-staking/src/multitest.rs @@ -21,6 +21,11 @@ use crate::msg::{AuthorizedEndpoint, ReceiveVirtualStake, StakeInfo}; const OSMO: &str = "osmo"; 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; + // Shortcut setuping all needed contracts // // Returns vault and external staking proxies @@ -37,6 +42,7 @@ 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), }; let staking_init = StakingInitInfo { @@ -59,6 +65,7 @@ fn setup<'app>( vault.contract_addr.to_string(), unbond_period, remote_contact, + Decimal::percent(SLASHING_PERCENTAGE), ) .call(owner)?; @@ -78,7 +85,7 @@ 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(5)); + assert_eq!(max_slash.max_slash, Decimal::percent(SLASHING_PERCENTAGE)); } #[test] diff --git a/contracts/provider/external-staking/src/state.rs b/contracts/provider/external-staking/src/state.rs index 9a4f5df7..e2245cd4 100644 --- a/contracts/provider/external-staking/src/state.rs +++ b/contracts/provider/external-staking/src/state.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{BlockInfo, Timestamp, Uint128, Uint256}; +use cosmwasm_std::{BlockInfo, Decimal, Timestamp, Uint128, Uint256}; use mesh_apis::vault_api::VaultApiHelper; use crate::points_alignment::PointsAlignment; @@ -13,8 +13,10 @@ pub struct Config { pub rewards_denom: String, /// Vault contract address pub vault: VaultApiHelper, - /// Ubbounding period for claims in seconds + /// Unbonding period for claims in seconds pub unbonding_period: u64, + /// Max slash percentage (from InstantiateMsg, maybe later from the chain) + pub max_slashing: Decimal, } /// All single stake related information - entry per `(user, validator)` pair, including diff --git a/contracts/provider/native-staking-proxy/src/multitest.rs b/contracts/provider/native-staking-proxy/src/multitest.rs index 328190b5..e4d41677 100644 --- a/contracts/provider/native-staking-proxy/src/multitest.rs +++ b/contracts/provider/native-staking-proxy/src/multitest.rs @@ -68,6 +68,7 @@ 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), }) .unwrap(), label: None, diff --git a/contracts/provider/native-staking/src/contract.rs b/contracts/provider/native-staking/src/contract.rs index 70c64897..70747417 100644 --- a/contracts/provider/native-staking/src/contract.rs +++ b/contracts/provider/native-staking/src/contract.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{from_slice, Addr, DepsMut, Reply, Response, SubMsgResponse}; +use cosmwasm_std::{from_slice, Addr, Decimal, DepsMut, Reply, Response, SubMsgResponse}; use cw2::set_contract_version; use cw_storage_plus::{Item, Map}; use cw_utils::parse_instantiate_response_data; @@ -18,9 +18,6 @@ pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const REPLY_ID_INSTANTIATE: u64 = 2; -// TODO: Hardcoded for now. Revisit for v1. -pub const MAX_SLASH_PERCENTAGE: u64 = 10; - pub struct NativeStakingContract<'a> { pub config: Item<'a, Config>, /// Map of proxy contract address by owner address @@ -50,11 +47,17 @@ impl NativeStakingContract<'_> { ctx: InstantiateCtx, denom: String, proxy_code_id: u64, + max_slashing: Decimal, ) -> Result { + if max_slashing > Decimal::one() { + return Err(ContractError::InvalidMaxSlashing); + } + let config = Config { denom, proxy_code_id, vault: ctx.info.sender, + max_slashing, }; self.config.save(ctx.deps.storage, &config)?; set_contract_version(ctx.deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; diff --git a/contracts/provider/native-staking/src/error.rs b/contracts/provider/native-staking/src/error.rs index 9b487503..ffb4a4e7 100644 --- a/contracts/provider/native-staking/src/error.rs +++ b/contracts/provider/native-staking/src/error.rs @@ -21,4 +21,7 @@ pub enum ContractError { #[error("Missing instantiate reply data")] NoInstantiateData {}, + + #[error("You cannot use a max slashing rate over 1.0 (100%)")] + InvalidMaxSlashing, } diff --git a/contracts/provider/native-staking/src/local_staking_api.rs b/contracts/provider/native-staking/src/local_staking_api.rs index 2df01703..6cf056e8 100644 --- a/contracts/provider/native-staking/src/local_staking_api.rs +++ b/contracts/provider/native-staking/src/local_staking_api.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{ensure_eq, from_slice, to_binary, Binary, Decimal, Response, SubMsg, WasmMsg}; +use cosmwasm_std::{ensure_eq, from_slice, to_binary, Binary, Response, SubMsg, WasmMsg}; use cw_utils::must_pay; use sylvia::types::QueryCtx; use sylvia::{contract, types::ExecCtx}; @@ -6,12 +6,13 @@ use sylvia::{contract, types::ExecCtx}; #[allow(unused_imports)] use mesh_apis::local_staking_api::{self, LocalStakingApi, MaxSlashResponse}; -use crate::contract::{NativeStakingContract, MAX_SLASH_PERCENTAGE, REPLY_ID_INSTANTIATE}; +use crate::contract::{NativeStakingContract, REPLY_ID_INSTANTIATE}; use crate::error::ContractError; use crate::msg::StakeMsg; // FIXME: Move to sylvia contract macro use crate::contract::BoundQuerier; +use crate::state::Config; #[contract] #[messages(local_staking_api as LocalStakingApi)] @@ -77,11 +78,11 @@ impl LocalStakingApi for NativeStakingContract<'_> { } /// Returns the maximum percentage that can be slashed - /// TODO: Any way to query this from the chain? Or we just pass in InstantiateMsg? #[msg(query)] - fn max_slash(&self, _ctx: QueryCtx) -> Result { + fn max_slash(&self, ctx: QueryCtx) -> Result { + let Config { max_slashing, .. } = self.config.load(ctx.deps.storage)?; Ok(MaxSlashResponse { - max_slash: Decimal::percent(MAX_SLASH_PERCENTAGE), + max_slash: max_slashing, }) } } diff --git a/contracts/provider/native-staking/src/multitest.rs b/contracts/provider/native-staking/src/multitest.rs index 9118221a..01dbc67d 100644 --- a/contracts/provider/native-staking/src/multitest.rs +++ b/contracts/provider/native-staking/src/multitest.rs @@ -15,6 +15,12 @@ use crate::msg::{OwnerByProxyResponse, ProxyByOwnerResponse}; const OSMO: &str = "OSMO"; +const SLASHING_PERCENTAGE: u64 = 15; + +fn slashing_rate() -> Decimal { + Decimal::percent(SLASHING_PERCENTAGE) +} + #[test] fn instantiation() { let app = App::default(); @@ -25,7 +31,11 @@ fn instantiation() { let staking_code = contract::multitest_utils::CodeId::store_code(&app); let staking = staking_code - .instantiate(OSMO.to_owned(), staking_proxy_code.code_id()) + .instantiate( + OSMO.to_owned(), + staking_proxy_code.code_id(), + slashing_rate(), + ) .with_label("Staking") .call(owner) .unwrap(); @@ -34,7 +44,7 @@ fn instantiation() { assert_eq!(config.denom, OSMO); let res = staking.local_staking_api_proxy().max_slash().unwrap(); - assert_eq!(res.max_slash, Decimal::percent(10)); + assert_eq!(res.max_slash, slashing_rate()); } #[test] @@ -60,7 +70,11 @@ fn receiving_stake() { let staking_code = contract::multitest_utils::CodeId::store_code(&app); let staking = staking_code - .instantiate(OSMO.to_owned(), staking_proxy_code.code_id()) + .instantiate( + OSMO.to_owned(), + staking_proxy_code.code_id(), + slashing_rate(), + ) .with_label("Staking") .call(owner) .unwrap(); @@ -196,6 +210,7 @@ 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(), }) .unwrap(), label: None, diff --git a/contracts/provider/native-staking/src/state.rs b/contracts/provider/native-staking/src/state.rs index 3370f8ca..825a56b2 100644 --- a/contracts/provider/native-staking/src/state.rs +++ b/contracts/provider/native-staking/src/state.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Decimal}; #[cw_serde] pub struct Config { @@ -11,4 +11,7 @@ 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, }