1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use cosmwasm_std::{
    attr, CosmosMsg, Deps, DepsMut, HandleResponse, HumanAddr, MessageInfo, StdError, StdResult,
    Storage,
};
use cw_storage_plus::Item;

use crate::admin::{Admin, AdminError};

// this is copied from cw4
// TODO: pull into cw0 as common dep
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
pub struct HooksResponse {
    pub hooks: Vec<HumanAddr>,
}

#[derive(Error, Debug, PartialEq)]
pub enum HookError {
    #[error("{0}")]
    Std(#[from] StdError),

    #[error("{0}")]
    Admin(#[from] AdminError),

    #[error("Given address already registered as a hook")]
    HookAlreadyRegistered {},

    #[error("Given address not registered as a hook")]
    HookNotRegistered {},
}

// store all hook addresses in one item. We cannot have many of them before the contract becomes unusable anyway.
pub struct Hooks<'a>(Item<'a, Vec<HumanAddr>>);

impl<'a> Hooks<'a> {
    pub const fn new(storage_key: &'a str) -> Self {
        Hooks(Item::new(storage_key))
    }

    pub fn add_hook(&self, storage: &mut dyn Storage, addr: HumanAddr) -> Result<(), HookError> {
        let mut hooks = self.0.may_load(storage)?.unwrap_or_default();
        if !hooks.iter().any(|h| h == &addr) {
            hooks.push(addr);
        } else {
            return Err(HookError::HookAlreadyRegistered {});
        }
        Ok(self.0.save(storage, &hooks)?)
    }

    pub fn remove_hook(&self, storage: &mut dyn Storage, addr: HumanAddr) -> Result<(), HookError> {
        let mut hooks = self.0.load(storage)?;
        if let Some(p) = hooks.iter().position(|x| x == &addr) {
            hooks.remove(p);
        } else {
            return Err(HookError::HookNotRegistered {});
        }
        Ok(self.0.save(storage, &hooks)?)
    }

    pub fn prepare_hooks<F: Fn(HumanAddr) -> StdResult<CosmosMsg>>(
        &self,
        storage: &dyn Storage,
        prep: F,
    ) -> StdResult<Vec<CosmosMsg>> {
        self.0
            .may_load(storage)?
            .unwrap_or_default()
            .into_iter()
            .map(prep)
            .collect()
    }

    pub fn handle_add_hook(
        &self,
        admin: &Admin,
        deps: DepsMut,
        info: MessageInfo,
        addr: HumanAddr,
    ) -> Result<HandleResponse, HookError> {
        admin.assert_admin(deps.as_ref(), &info.sender)?;
        self.add_hook(deps.storage, addr.clone())?;

        let attributes = vec![
            attr("action", "add_hook"),
            attr("hook", addr),
            attr("sender", info.sender),
        ];
        Ok(HandleResponse {
            messages: vec![],
            attributes,
            data: None,
        })
    }

    pub fn handle_remove_hook(
        &self,
        admin: &Admin,
        deps: DepsMut,
        info: MessageInfo,
        addr: HumanAddr,
    ) -> Result<HandleResponse, HookError> {
        admin.assert_admin(deps.as_ref(), &info.sender)?;
        self.remove_hook(deps.storage, addr.clone())?;

        let attributes = vec![
            attr("action", "remove_hook"),
            attr("hook", addr),
            attr("sender", info.sender),
        ];
        Ok(HandleResponse {
            messages: vec![],
            attributes,
            data: None,
        })
    }

    pub fn query_hooks(&self, deps: Deps) -> StdResult<HooksResponse> {
        let hooks = self.0.may_load(deps.storage)?.unwrap_or_default();
        Ok(HooksResponse { hooks })
    }
}

// TODO: add test coverage