abstract_sdk/apis/
verify.rs

1//! # Verification
2//! The `Verify` struct provides helper functions that enable the contract to verify if the sender is an Abstract Account, Account admin, etc.
3use abstract_std::{
4    objects::ownership::nested_admin::assert_account_calling_to_as_admin_is_self,
5    objects::{registry::RegistryContract, AccountId},
6    registry::Account,
7};
8use cosmwasm_std::{Addr, Deps, Env};
9
10use super::AbstractApi;
11use crate::{
12    cw_helpers::ApiQuery,
13    features::{AbstractRegistryAccess, ModuleIdentification},
14    AbstractSdkError, AbstractSdkResult,
15};
16
17/// Verify if an addresses is associated with an Abstract Account.
18pub trait AccountVerification: AbstractRegistryAccess + ModuleIdentification {
19    /**
20        API for querying and verifying a sender's identity in the context of Abstract Accounts.
21
22        # Example
23        ```
24        use abstract_sdk::prelude::*;
25        # use cosmwasm_std::testing::mock_dependencies;
26        # use abstract_sdk::mock_module::MockModule;
27        # use abstract_testing::prelude::*;
28        # let deps = mock_dependencies();
29        # let env = mock_env_validated(deps.api);
30        # let account = admin_account(deps.api);
31        # let module = MockModule::new(deps.api, account);
32
33        let acc_registry: AccountRegistry<MockModule>  = module.account_registry(deps.as_ref()).unwrap();
34        ```
35    */
36    fn account_registry<'a>(
37        &'a self,
38        deps: Deps<'a>,
39    ) -> AbstractSdkResult<AccountRegistry<'a, Self>> {
40        let vc = self.abstract_registry(deps)?;
41        Ok(AccountRegistry {
42            base: self,
43            deps,
44            registry: vc,
45        })
46    }
47}
48
49impl<T> AccountVerification for T where T: AbstractRegistryAccess + ModuleIdentification {}
50
51impl<T: AccountVerification> AbstractApi<T> for AccountRegistry<'_, T> {
52    const API_ID: &'static str = "AccountRegistry";
53
54    fn base(&self) -> &T {
55        self.base
56    }
57    fn deps(&self) -> Deps {
58        self.deps
59    }
60}
61
62/**
63    API for querying and verifying a sender's identity in the context of Abstract Accounts.
64
65    # Example
66    ```
67    use abstract_sdk::prelude::*;
68    # use cosmwasm_std::testing::mock_dependencies;
69    # use abstract_sdk::mock_module::MockModule;
70    # use abstract_testing::prelude::*;
71    # let deps = mock_dependencies();
72    # let env = mock_env_validated(deps.api);
73    # let account = admin_account(deps.api);
74    # let module = MockModule::new(deps.api, account);
75
76    let acc_registry: AccountRegistry<MockModule>  = module.account_registry(deps.as_ref()).unwrap();
77    ```
78*/
79#[derive(Clone)]
80pub struct AccountRegistry<'a, T: AccountVerification> {
81    base: &'a T,
82    deps: Deps<'a>,
83    registry: RegistryContract,
84}
85
86impl<T: AccountVerification> AccountRegistry<'_, T> {
87    /// Verify if the provided address is indeed an Abstract Account.
88    pub fn assert_is_account(&self, maybe_account: &Addr) -> AbstractSdkResult<Account> {
89        self.registry
90            .assert_account(maybe_account, &self.deps.querier)
91            .map_err(|error| self.wrap_query_error(error))
92    }
93
94    /// Get the account for a given account id.
95    pub fn account(&self, account_id: &AccountId) -> AbstractSdkResult<Account> {
96        self.registry
97            .account(account_id, &self.deps.querier)
98            .map_err(|error| self.wrap_query_error(error))
99    }
100
101    /// Get AccountId for given address.
102    pub fn account_id(&self, maybe_account_contract_addr: &Addr) -> AbstractSdkResult<AccountId> {
103        self.registry
104            .account_id(maybe_account_contract_addr, &self.deps.querier)
105            .map_err(|error| self.wrap_query_error(error))
106    }
107
108    /// Get namespace registration fee
109    pub fn namespace_registration_fee(&self) -> AbstractSdkResult<Option<cosmwasm_std::Coin>> {
110        self.registry
111            .namespace_registration_fee(&self.deps.querier)
112            .map_err(|error| self.wrap_query_error(error))
113    }
114
115    /// Verify if the provided address is indeed an Abstract Account AND if the current execution has admin rights.
116    pub fn assert_is_account_admin(
117        &self,
118        env: &Env,
119        maybe_account: &Addr,
120    ) -> AbstractSdkResult<Account> {
121        let account = self.assert_is_account(maybe_account)?;
122
123        if !assert_account_calling_to_as_admin_is_self(&self.deps.querier, env, maybe_account) {
124            return Err(AbstractSdkError::OnlyAdmin {});
125        }
126        Ok(account)
127    }
128}
129
130#[cfg(test)]
131mod test {
132    #![allow(clippy::needless_borrows_for_generic_args)]
133    use super::*;
134
135    use crate::{apis::traits::test::abstract_api_test, AbstractSdkError};
136    use abstract_std::{
137        account::state::ACCOUNT_ID,
138        objects::{account::AccountTrace, module::ModuleId, registry::RegistryError},
139        registry::{self, state::ACCOUNT_ADDRESSES},
140    };
141    use abstract_testing::prelude::*;
142    use cosmwasm_std::{testing::*, Coin};
143
144    struct MockBinding {}
145
146    impl AbstractRegistryAccess for MockBinding {
147        fn abstract_registry(&self, deps: Deps) -> AbstractSdkResult<RegistryContract> {
148            RegistryContract::new(deps, 1).map_err(Into::into)
149        }
150    }
151
152    impl ModuleIdentification for MockBinding {
153        fn module_id(&self) -> ModuleId<'static> {
154            ModuleId::from(TEST_MODULE_ID)
155        }
156    }
157
158    pub const SECOND_TEST_ACCOUNT_ID: AccountId = AccountId::const_new(2, AccountTrace::Local);
159
160    mod assert_account {
161
162        use super::*;
163
164        #[coverage_helper::test]
165        fn not_account_fails() {
166            let mut deps = mock_dependencies();
167            let not_account = Account::new(deps.api.addr_make("not_account"));
168            let base = test_account(deps.api);
169
170            deps.querier = MockQuerierBuilder::new(deps.api)
171                // Setup the addresses as if the Account was registered
172                .account(&not_account, TEST_ACCOUNT_ID)
173                // update the account to be account of a different Account
174                .account(&base, SECOND_TEST_ACCOUNT_ID)
175                .with_contract_item(not_account.addr(), ACCOUNT_ID, &SECOND_TEST_ACCOUNT_ID)
176                .build();
177
178            let binding = MockBinding {};
179
180            let res = binding
181                .account_registry(deps.as_ref())
182                .unwrap()
183                .assert_is_account(not_account.addr());
184
185            let expected_err = AbstractSdkError::ApiQuery {
186                api: AccountRegistry::<MockBinding>::api_id(),
187                module_id: binding.module_id().to_owned(),
188                error: Box::new(
189                    RegistryError::NotAccount(not_account.addr().clone(), SECOND_TEST_ACCOUNT_ID)
190                        .into(),
191                ),
192            };
193            assert_eq!(res.unwrap_err(), expected_err);
194        }
195
196        #[coverage_helper::test]
197        fn inactive_account_fails() {
198            let mut deps = mock_dependencies();
199            let abstr = AbstractMockAddrs::new(deps.api);
200
201            deps.querier = MockQuerierBuilder::default()
202                .with_contract_item(abstr.account.addr(), ACCOUNT_ID, &TEST_ACCOUNT_ID)
203                .with_contract_map_key(&abstr.registry, ACCOUNT_ADDRESSES, &TEST_ACCOUNT_ID)
204                .build();
205
206            let binding = MockBinding {};
207
208            let res = binding
209                .account_registry(deps.as_ref())
210                .unwrap()
211                .assert_is_account(abstr.account.addr());
212
213            let expected_err = AbstractSdkError::ApiQuery {
214                api: AccountRegistry::<MockBinding>::api_id(),
215                module_id: binding.module_id().to_owned(),
216                error: Box::new(
217                    RegistryError::UnknownAccountId {
218                        account_id: TEST_ACCOUNT_ID,
219                        registry_addr: abstr.registry,
220                    }
221                    .into(),
222                ),
223            };
224            assert_eq!(res.unwrap_err(), expected_err);
225        }
226
227        #[coverage_helper::test]
228        fn returns_account() {
229            let mut deps = mock_dependencies();
230            let account = test_account(deps.api);
231
232            deps.querier = abstract_mock_querier_builder(deps.api)
233                .account(&account, TEST_ACCOUNT_ID)
234                .build();
235
236            let binding = MockBinding {};
237
238            let registry = binding.account_registry(deps.as_ref()).unwrap();
239            let res = registry.assert_is_account(account.addr());
240
241            assert_eq!(res, Ok(account.clone()));
242
243            let account_id = registry.account_id(account.addr());
244            assert_eq!(account_id, Ok(TEST_ACCOUNT_ID));
245        }
246    }
247
248    #[coverage_helper::test]
249    fn namespace_fee() {
250        let mut deps = mock_dependencies();
251
252        deps.querier = abstract_mock_querier(deps.api);
253
254        let binding = MockBinding {};
255
256        // Namespace registration fee query
257        {
258            let registry = binding.account_registry(deps.as_ref()).unwrap();
259            let res = registry.namespace_registration_fee();
260
261            assert_eq!(res, Ok(None));
262        }
263
264        let abstr = AbstractMockAddrs::new(deps.api);
265        deps.querier = abstract_mock_querier_builder(deps.api)
266            .with_contract_item(
267                &abstr.registry,
268                registry::state::CONFIG,
269                &registry::Config {
270                    security_enabled: false,
271                    namespace_registration_fee: Some(Coin::new(42_u128, "foo")),
272                },
273            )
274            .build();
275
276        let registry = binding.account_registry(deps.as_ref()).unwrap();
277        let res = registry.namespace_registration_fee();
278
279        assert_eq!(res, Ok(Some(Coin::new(42_u128, "foo"))));
280    }
281
282    #[coverage_helper::test]
283    fn abstract_api() {
284        let mut deps = mock_dependencies();
285        deps.querier = abstract_mock_querier(deps.api);
286        let module = MockBinding {};
287
288        let account_registry = module.account_registry(deps.as_ref()).unwrap();
289
290        abstract_api_test(account_registry);
291    }
292}