1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use thiserror::Error;
4
5use cosmwasm_std::{Addr, Decimal, StdError, StdResult, Storage};
6use cw_storage_plus::Item;
7
8pub struct Slashers<'a>(Item<'a, Vec<Addr>>);
10
11impl<'a> Slashers<'a> {
12 pub const fn new(storage_key: &'a str) -> Self {
13 Slashers(Item::new(storage_key))
14 }
15
16 pub fn instantiate(&self, storage: &mut dyn Storage) -> StdResult<()> {
17 self.0.save(storage, &vec![])
18 }
19
20 pub fn add_slasher(&self, storage: &mut dyn Storage, addr: Addr) -> Result<(), SlasherError> {
21 let mut slashers = self.0.load(storage)?;
22 if !slashers.iter().any(|h| h == &addr) {
23 slashers.push(addr);
24 } else {
25 return Err(SlasherError::SlasherAlreadyRegistered(addr.to_string()));
26 }
27 Ok(self.0.save(storage, &slashers)?)
28 }
29
30 pub fn remove_slasher(
31 &self,
32 storage: &mut dyn Storage,
33 addr: Addr,
34 ) -> Result<(), SlasherError> {
35 let mut slashers = self.0.load(storage)?;
36 if let Some(p) = slashers.iter().position(|x| x == &addr) {
37 slashers.remove(p);
38 } else {
39 return Err(SlasherError::SlasherNotRegistered(addr.to_string()));
40 }
41 Ok(self.0.save(storage, &slashers)?)
42 }
43
44 pub fn is_slasher(&self, storage: &dyn Storage, addr: &Addr) -> StdResult<bool> {
45 let slashers = self.0.load(storage)?;
46 Ok(slashers.contains(addr))
47 }
48
49 pub fn list_slashers(&self, storage: &dyn Storage) -> StdResult<Vec<String>> {
50 let slashers = self.0.load(storage)?;
51 Ok(slashers.into_iter().map(String::from).collect())
52 }
53}
54
55#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
76#[serde(rename_all = "snake_case")]
77pub enum SlashMsg {
78 AddSlasher { addr: String },
80 RemoveSlasher { addr: String },
82 Slash { addr: String, portion: Decimal },
84}
85
86#[derive(Error, Debug, PartialEq)]
87pub enum SlasherError {
88 #[error("{0}")]
89 Std(#[from] StdError),
90
91 #[error("Given address already registered as a hook")]
92 SlasherAlreadyRegistered(String),
93
94 #[error("Given address not registered as a hook")]
95 SlasherNotRegistered(String),
96
97 #[error("Invalid portion {0}, must be (0, 1]")]
98 InvalidPortion(Decimal),
99}
100
101pub fn validate_portion(portion: Decimal) -> Result<(), SlasherError> {
102 match portion.is_zero() || portion > Decimal::one() {
103 true => Err(SlasherError::InvalidPortion(portion)),
104 false => Ok(()),
105 }
106}