abstract_core/objects/
nested_admin.rs

1use cosmwasm_std::{
2    attr, Addr, CustomQuery, Deps, DepsMut, MessageInfo, QuerierWrapper, Response, StdError,
3    StdResult,
4};
5use cw_controllers::{Admin, AdminError, AdminResponse};
6use schemars::JsonSchema;
7
8use crate::{
9    manager::{self, state::AccountInfo},
10    objects::gov_type::GovernanceDetails,
11};
12
13/// Max manager admin recursion
14pub const MAX_ADMIN_RECURSION: usize = 2;
15
16/// Abstract Admin object
17/// This object has same api to the [cw_controllers::Admin]
18/// With added query_account_owner method (will get top-level owner in case of sub-accounts)
19/// but allows top-level abstract account owner to have admin privileges on the module
20pub struct NestedAdmin<'a>(Admin<'a>);
21
22impl<'a> NestedAdmin<'a> {
23    pub const fn new(namespace: &'a str) -> Self {
24        NestedAdmin(Admin::new(namespace))
25    }
26
27    pub fn set<Q: CustomQuery>(&self, deps: DepsMut<Q>, admin: Option<Addr>) -> StdResult<()> {
28        self.0.set(deps, admin)
29    }
30
31    pub fn get<Q: CustomQuery>(&self, deps: Deps<Q>) -> StdResult<Option<Addr>> {
32        self.0.get(deps)
33    }
34
35    pub fn is_admin<Q: CustomQuery>(&self, deps: Deps<Q>, caller: &Addr) -> StdResult<bool> {
36        match self.0.get(deps)? {
37            Some(owner) => {
38                // Initial check if directly called by the owner
39                if caller == owner {
40                    Ok(true)
41                } else {
42                    // Check if top level owner address is equal to the caller
43                    Ok(query_top_level_owner(&deps.querier, owner)
44                        .map(|owner| owner == caller)
45                        .unwrap_or(false))
46                }
47            }
48            None => Ok(false),
49        }
50    }
51
52    pub fn assert_admin<Q: CustomQuery>(
53        &self,
54        deps: Deps<Q>,
55        caller: &Addr,
56    ) -> Result<(), AdminError> {
57        if !self.is_admin(deps, caller)? {
58            Err(AdminError::NotAdmin {})
59        } else {
60            Ok(())
61        }
62    }
63
64    pub fn execute_update_admin<C, Q: CustomQuery>(
65        &self,
66        deps: DepsMut<Q>,
67        info: MessageInfo,
68        new_admin: Option<Addr>,
69    ) -> Result<Response<C>, AdminError>
70    where
71        C: Clone + core::fmt::Debug + PartialEq + JsonSchema,
72    {
73        self.assert_admin(deps.as_ref(), &info.sender)?;
74
75        let admin_str = match new_admin.as_ref() {
76            Some(admin) => admin.to_string(),
77            None => "None".to_string(),
78        };
79        let attributes = vec![
80            attr("action", "update_admin"),
81            attr("admin", admin_str),
82            attr("sender", info.sender),
83        ];
84
85        self.set(deps, new_admin)?;
86
87        Ok(Response::new().add_attributes(attributes))
88    }
89
90    // This method queries direct module owner
91    pub fn query_admin<Q: CustomQuery>(&self, deps: Deps<Q>) -> StdResult<AdminResponse> {
92        self.0.query_admin(deps)
93    }
94
95    // This method tries to get top-level account owner
96    pub fn query_account_owner<Q: CustomQuery>(&self, deps: Deps<Q>) -> StdResult<AdminResponse> {
97        let admin = match self.0.get(deps)? {
98            Some(owner) => Some(query_top_level_owner(&deps.querier, owner).map_err(|_| {
99                StdError::generic_err(
100                    "Failed to query top level owner. Make sure this module is owned by the manager",
101                )
102            })?),
103            None => None,
104        };
105        Ok(AdminResponse {
106            admin: admin.map(|addr| addr.into_string()),
107        })
108    }
109}
110
111pub fn query_top_level_owner<Q: CustomQuery>(
112    querier: &QuerierWrapper<Q>,
113    maybe_manager: Addr,
114) -> StdResult<Addr> {
115    // Starting from (potentially)manager that owns this module
116    let mut current = manager::state::INFO.query(querier, maybe_manager.clone());
117    // Get sub-accounts until we get non-sub-account governance or reach recursion limit
118    for _ in 0..MAX_ADMIN_RECURSION {
119        match &current {
120            Ok(AccountInfo {
121                governance_details: GovernanceDetails::SubAccount { manager, .. },
122                ..
123            }) => {
124                current = manager::state::INFO.query(querier, manager.clone());
125            }
126            _ => break,
127        }
128    }
129
130    // Get top level account owner address
131    current.and_then(|info| {
132        info.governance_details
133            .owner_address()
134            .ok_or(StdError::generic_err("Top level account got renounced"))
135    })
136}
137
138#[cosmwasm_schema::cw_serde]
139pub struct TopLevelOwnerResponse {
140    pub address: Addr,
141}