abstract_std/objects/ownership/
nested_admin.rs

1use crate::{
2    account::state::{CALLING_TO_AS_ADMIN, CALLING_TO_AS_ADMIN_WILD_CARD},
3    objects::{gov_type::GovernanceDetails, ownership::Ownership},
4};
5
6use cosmwasm_std::{
7    attr, Addr, CustomQuery, Deps, DepsMut, Env, MessageInfo, QuerierWrapper, Response, StdError,
8    StdResult,
9};
10use cw_controllers::{Admin, AdminError, AdminResponse};
11use schemars::JsonSchema;
12
13use super::query_ownership;
14
15/// Max account admin recursion
16pub const MAX_ADMIN_RECURSION: usize = 2;
17
18/// # Abstract Admin Object
19/// This object has a similar api to the [cw_controllers::Admin] object but incorporates nested ownership and abstract-specific Admin checks.
20///
21/// The ownership of a contract can be nested, meaning that the owner of the contract can be owned by another contract (or address) and so on.
22///
23/// By using this structure we allow both the direct owner as well as the top-level owner to have permissions to perform actions that are gated by this object.
24///
25/// See [NestedAdmin::assert_admin] for more details on how the admin rights are checked.
26pub struct NestedAdmin(Admin);
27
28impl NestedAdmin {
29    pub const fn new(namespace: &'static str) -> Self {
30        NestedAdmin(Admin::new(namespace))
31    }
32
33    pub fn set<Q: CustomQuery>(&self, deps: DepsMut<Q>, admin: Option<Addr>) -> StdResult<()> {
34        self.0.set(deps, admin)
35    }
36
37    pub fn get<Q: CustomQuery>(&self, deps: Deps<Q>) -> StdResult<Option<Addr>> {
38        self.0.get(deps)
39    }
40
41    /// See [NestedAdmin::assert_admin] for more details.
42    pub fn is_admin<Q: CustomQuery>(
43        &self,
44        deps: Deps<Q>,
45        env: &Env,
46        caller: &Addr,
47    ) -> StdResult<bool> {
48        match self.0.get(deps)? {
49            Some(admin) => Self::is_admin_custom(&deps.querier, env, caller, admin),
50            None => Ok(false),
51        }
52    }
53
54    /// See [NestedAdmin::assert_admin] for more details.
55    pub fn is_admin_custom<Q: CustomQuery>(
56        querier: &QuerierWrapper<Q>,
57        env: &Env,
58        caller: &Addr,
59        admin: Addr,
60    ) -> StdResult<bool> {
61        // Initial check if directly called by the admin
62        if caller == admin && assert_account_calling_to_as_admin_is_self(querier, env, caller) {
63            // If the caller is the admin, we still need to check that
64            // if it's an account it's authorized to act as an admin
65
66            Ok(true)
67        } else {
68            // Check if top level owner address is equal to the caller
69            Ok(query_top_level_owner_addr(querier, admin)
70                .map(|admin| admin == caller)
71                .unwrap_or(false))
72        }
73    }
74
75    /// Assert that the caller is allowed to perform admin actions.
76    ///
77    /// This method will pass in two specific scenarios:
78    ///
79    /// - If the caller is the direct admin of the contract. I.e. the admin stored in this contract. AND the state `CALLING_TO_AS_ADMIN` is set to the contract address or a wildcard.
80    /// - If the caller is the **top-level** admin of the chain of ownership, starting from this contract.
81    pub fn assert_admin<Q: CustomQuery>(
82        &self,
83        deps: Deps<Q>,
84        env: &Env,
85        caller: &Addr,
86    ) -> Result<(), AdminError> {
87        if !self.is_admin(deps, env, caller)? {
88            Err(AdminError::NotAdmin {})
89        } else {
90            Ok(())
91        }
92    }
93
94    /// Assert that the caller is allowed to perform admin actions.
95    ///
96    /// This method will pass in two specific scenarios:
97    ///
98    /// - If the caller is the direct admin of the contract. I.e. the admin stored in this contract. AND the state `CALLING_TO_AS_ADMIN` is set to the contract address or a wildcard.
99    /// - If the caller is the **top-level** admin of the chain of ownership, starting from this contract.
100    pub fn assert_admin_custom<Q: CustomQuery>(
101        querier: &QuerierWrapper<Q>,
102        env: &Env,
103        caller: &Addr,
104        admin: Addr,
105    ) -> Result<(), AdminError> {
106        if !Self::is_admin_custom(querier, env, caller, admin)? {
107            Err(AdminError::NotAdmin {})
108        } else {
109            Ok(())
110        }
111    }
112
113    pub fn execute_update_admin<C, Q: CustomQuery>(
114        &self,
115        deps: DepsMut<Q>,
116        env: &Env,
117        info: MessageInfo,
118        new_admin: Option<Addr>,
119    ) -> Result<Response<C>, AdminError>
120    where
121        C: Clone + core::fmt::Debug + PartialEq + JsonSchema,
122    {
123        self.assert_admin(deps.as_ref(), env, &info.sender)?;
124
125        let admin_str = match new_admin.as_ref() {
126            Some(admin) => admin.to_string(),
127            None => "None".to_string(),
128        };
129        let attributes = vec![
130            attr("action", "update_admin"),
131            attr("admin", admin_str),
132            attr("sender", info.sender),
133        ];
134
135        self.set(deps, new_admin)?;
136
137        Ok(Response::new().add_attributes(attributes))
138    }
139
140    // This method queries direct module owner
141    pub fn query_admin<Q: CustomQuery>(&self, deps: Deps<Q>) -> StdResult<AdminResponse> {
142        self.0.query_admin(deps)
143    }
144
145    // This method tries to get top-level account owner
146    pub fn query_account_owner<Q: CustomQuery>(&self, deps: Deps<Q>) -> StdResult<AdminResponse> {
147        let admin = match self.0.get(deps)? {
148            Some(owner) => Some(query_top_level_owner_addr(&deps.querier, owner).map_err(|_| {
149                StdError::generic_err(
150                    "Failed to query top level owner. Make sure this module is owned by the account",
151                )
152            })?),
153            None => None,
154        };
155        Ok(AdminResponse {
156            admin: admin.map(|addr| addr.into_string()),
157        })
158    }
159}
160
161pub fn query_top_level_owner_addr<Q: CustomQuery>(
162    querier: &QuerierWrapper<Q>,
163    maybe_account: Addr,
164) -> StdResult<Addr> {
165    // Get top level account owner address
166    query_top_level_owner(querier, maybe_account).and_then(|ownership| {
167        ownership
168            .owner
169            .owner_address(&querier.into_empty())
170            .ok_or(StdError::generic_err("Top level account got renounced"))
171    })
172}
173
174pub fn query_top_level_owner<Q: CustomQuery>(
175    querier: &QuerierWrapper<Q>,
176    maybe_account: Addr,
177) -> StdResult<Ownership<Addr>> {
178    // Starting from (potentially)account that owns this module
179    let mut current = query_ownership(querier, maybe_account);
180    // Get sub-accounts until we get non-sub-account governance or reach recursion limit
181    for _ in 0..MAX_ADMIN_RECURSION {
182        match current {
183            Ok(Ownership {
184                owner: GovernanceDetails::SubAccount { account },
185                ..
186            }) => {
187                current = query_ownership(querier, account);
188            }
189            _ => break,
190        }
191    }
192
193    current
194}
195
196/// Assert that the account has a valid calling to the contract as an admin.
197pub fn assert_account_calling_to_as_admin_is_self<Q: CustomQuery>(
198    querier: &QuerierWrapper<Q>,
199    env: &Env,
200    maybe_account: &Addr,
201) -> bool {
202    CALLING_TO_AS_ADMIN
203        .query(querier, maybe_account.clone())
204        .map(|admin_call_to| {
205            admin_call_to == env.contract.address
206                || admin_call_to.as_str() == CALLING_TO_AS_ADMIN_WILD_CARD
207        })
208        .unwrap_or(false)
209}