abstract-sdk 0.19.2

Helpers for Abstract contracts
Documentation
//! # Verification
//! The `Verify` struct provides helper functions that enable the contract to verify if the sender is an Abstract Account, Account admin, etc.
use crate::{features::AbstractRegistryAccess, AbstractSdkError, AbstractSdkResult};
use abstract_core::{
    manager::state::ACCOUNT_ID,
    objects::AccountId,
    version_control::{state::ACCOUNT_ADDRESSES, AccountBase},
};
use cosmwasm_std::{Addr, Deps};

/// Verify if an addresses is associated with an Abstract Account.
pub trait AccountVerification: AbstractRegistryAccess {
    /**
        API for querying and verifying a sender's identity in the context of Abstract Accounts.

        # Example
        ```
        use abstract_sdk::prelude::*;
        # use cosmwasm_std::testing::mock_dependencies;
        # use abstract_sdk::mock_module::MockModule;
        # let module = MockModule::new();
        # let deps = mock_dependencies();

        let acc_registry: AccountRegistry<MockModule>  = module.account_registry(deps.as_ref());
        ```
    */
    fn account_registry<'a>(&'a self, deps: Deps<'a>) -> AccountRegistry<Self> {
        AccountRegistry { base: self, deps }
    }
}

impl<T> AccountVerification for T where T: AbstractRegistryAccess {}

/**
    API for querying and verifying a sender's identity in the context of Abstract Accounts.

    # Example
    ```
    use abstract_sdk::prelude::*;
    # use cosmwasm_std::testing::mock_dependencies;
    # use abstract_sdk::mock_module::MockModule;
    # let module = MockModule::new();
    # let deps = mock_dependencies();

    let acc_registry: AccountRegistry<MockModule>  = module.account_registry(deps.as_ref());
    ```
*/
#[derive(Clone)]
pub struct AccountRegistry<'a, T: AccountVerification> {
    base: &'a T,
    deps: Deps<'a>,
}

impl<'a, T: AccountVerification> AccountRegistry<'a, T> {
    /// Verify if the provided manager address is indeed a user.
    pub fn assert_manager(&self, maybe_manager: &Addr) -> AbstractSdkResult<AccountBase> {
        let account_id = self.account_id(maybe_manager)?;
        let account_base = self.account_base(&account_id)?;
        if account_base.manager.ne(maybe_manager) {
            Err(AbstractSdkError::NotManager(
                maybe_manager.clone(),
                account_id,
            ))
        } else {
            Ok(account_base)
        }
    }

    /// Verify if the provided proxy address is indeed a user.
    pub fn assert_proxy(&self, maybe_proxy: &Addr) -> AbstractSdkResult<AccountBase> {
        let account_id = self.account_id(maybe_proxy)?;
        let account_base = self.account_base(&account_id)?;
        if account_base.proxy.ne(maybe_proxy) {
            Err(AbstractSdkError::NotProxy(maybe_proxy.clone(), account_id))
        } else {
            Ok(account_base)
        }
    }

    /// Get the proxy address for a given account id.
    pub fn proxy_address(&self, account_id: &AccountId) -> AbstractSdkResult<Addr> {
        self.account_base(account_id)
            .map(|account_base| account_base.proxy)
    }

    /// Get the manager address for a given account id.
    pub fn manager_address(&self, account_id: &AccountId) -> AbstractSdkResult<Addr> {
        self.account_base(account_id)
            .map(|account_base| account_base.manager)
    }

    /// Get the account base for a given account id.
    pub fn account_base(&self, account_id: &AccountId) -> AbstractSdkResult<AccountBase> {
        let maybe_account = ACCOUNT_ADDRESSES.query(
            &self.deps.querier,
            self.base.abstract_registry(self.deps)?.address,
            account_id,
        )?;
        match maybe_account {
            None => Err(AbstractSdkError::UnknownAccountId {
                account_id: account_id.clone(),
                version_control_addr: self.base.abstract_registry(self.deps)?.address,
            }),
            Some(account_base) => Ok(account_base),
        }
    }

    fn account_id(&self, maybe_core_contract_addr: &Addr) -> AbstractSdkResult<AccountId> {
        ACCOUNT_ID
            .query(&self.deps.querier, maybe_core_contract_addr.clone())
            .map_err(|_| AbstractSdkError::FailedToQueryAccountId {
                contract_addr: maybe_core_contract_addr.clone(),
            })
    }
}

#[cfg(test)]
mod test {

    use super::*;
    use abstract_core::objects::account::AccountTrace;
    use abstract_core::objects::version_control::VersionControlContract;
    use abstract_testing::*;
    use cosmwasm_std::testing::*;

    use abstract_testing::prelude::*;
    use speculoos::prelude::*;

    struct MockBinding;

    impl AbstractRegistryAccess for MockBinding {
        fn abstract_registry(&self, _deps: Deps) -> AbstractSdkResult<VersionControlContract> {
            Ok(VersionControlContract {
                address: Addr::unchecked(TEST_VERSION_CONTROL),
            })
        }
    }

    pub const SECOND_TEST_ACCOUNT_ID: AccountId = AccountId::const_new(2, AccountTrace::Local);

    mod assert_proxy {

        use super::*;

        #[test]
        fn not_proxy_fails() {
            let mut deps = mock_dependencies();
            deps.querier = mocked_account_querier_builder()
                // Setup the addresses as if the Account was registered
                .account("not_manager", "not_proxy", TEST_ACCOUNT_ID)
                // update the proxy to be proxy of a different Account
                .account(TEST_MANAGER, TEST_PROXY, SECOND_TEST_ACCOUNT_ID)
                .builder()
                .with_contract_item("not_proxy", ACCOUNT_ID, &SECOND_TEST_ACCOUNT_ID)
                .build();

            let binding = MockBinding;

            let res = binding
                .account_registry(deps.as_ref())
                .assert_proxy(&Addr::unchecked("not_proxy"));

            assert_that!(res)
                .is_err()
                .matches(|e| matches!(e, AbstractSdkError::NotProxy(..)))
                .matches(|e| e.to_string().contains("not_proxy"));
        }

        #[test]
        fn inactive_account_fails() {
            let mut deps = mock_dependencies();

            deps.querier = MockQuerierBuilder::default()
                .with_contract_item(TEST_PROXY, ACCOUNT_ID, &TEST_ACCOUNT_ID)
                .with_contract_map_key(TEST_VERSION_CONTROL, ACCOUNT_ADDRESSES, &TEST_ACCOUNT_ID)
                .build();

            let binding = MockBinding;

            let res = binding
                .account_registry(deps.as_ref())
                .assert_proxy(&Addr::unchecked(TEST_PROXY));

            assert_that!(res)
                .is_err()
                .matches(|e| matches!(e, AbstractSdkError::UnknownAccountId { .. }))
                .matches(|e| {
                    e.to_string()
                        .contains(format!("Unknown Account id {}", TEST_ACCOUNT_ID).as_str())
                });
        }

        #[test]
        fn returns_core() {
            let mut deps = mock_dependencies();

            deps.querier = MockQuerierBuilder::default()
                .with_contract_item(TEST_PROXY, ACCOUNT_ID, &TEST_ACCOUNT_ID)
                .with_contract_map_entry(
                    TEST_VERSION_CONTROL,
                    ACCOUNT_ADDRESSES,
                    (&TEST_ACCOUNT_ID, test_account_base()),
                )
                .build();

            let binding = MockBinding;

            let res = binding
                .account_registry(deps.as_ref())
                .assert_proxy(&Addr::unchecked(TEST_PROXY));

            assert_that!(res).is_ok().is_equal_to(test_account_base());
        }

        #[test]
        fn errors_when_not_manager_of_returned_os() {
            let mut deps = mock_dependencies();

            deps.querier = MockQuerierBuilder::default()
                .with_contract_item(TEST_PROXY, ACCOUNT_ID, &TEST_ACCOUNT_ID)
                .with_contract_map_entry(
                    TEST_VERSION_CONTROL,
                    ACCOUNT_ADDRESSES,
                    (
                        &TEST_ACCOUNT_ID,
                        AccountBase {
                            manager: Addr::unchecked(TEST_MANAGER),
                            proxy: Addr::unchecked("not_poxry"),
                        },
                    ),
                )
                .build();

            let binding = MockBinding;

            let res = binding
                .account_registry(deps.as_ref())
                .assert_proxy(&Addr::unchecked(TEST_PROXY));

            assert_that!(res)
                .is_err()
                // .matches(|e| matches!(e, AbstractSdkError::Std(StdError::GenericErr { .. })))
                .matches(|e| e.to_string().contains("not the Proxy"));
        }
    }

    mod assert_manager {
        use super::*;

        #[test]
        fn not_manager_fails() {
            let mut deps = mock_dependencies();
            deps.querier = mocked_account_querier_builder()
                // Setup the addresses as if the Account was registered
                .account("not_manager", "not_proxy", TEST_ACCOUNT_ID)
                // update the proxy to be proxy of a different Account
                .account(TEST_MANAGER, TEST_PROXY, SECOND_TEST_ACCOUNT_ID)
                .builder()
                .with_contract_item("not_manager", ACCOUNT_ID, &SECOND_TEST_ACCOUNT_ID)
                .build();

            let binding = MockBinding;

            let res = binding
                .account_registry(deps.as_ref())
                .assert_manager(&Addr::unchecked("not_manager"));

            assert_that!(res)
                .is_err()
                .matches(|e| matches!(e, AbstractSdkError::NotManager(..)))
                .matches(|e| e.to_string().contains("not_manager is not the Manager"));
        }

        #[test]
        fn inactive_account_fails() {
            let mut deps = mock_dependencies();

            deps.querier = MockQuerierBuilder::default()
                .with_contract_item(TEST_MANAGER, ACCOUNT_ID, &TEST_ACCOUNT_ID)
                .with_contract_map_key(TEST_VERSION_CONTROL, ACCOUNT_ADDRESSES, &TEST_ACCOUNT_ID)
                .build();

            let binding = MockBinding;

            let res = binding
                .account_registry(deps.as_ref())
                .assert_manager(&Addr::unchecked(TEST_MANAGER));

            assert_that!(res)
                .is_err()
                .matches(|e| matches!(e, AbstractSdkError::UnknownAccountId { .. }))
                .matches(|e| {
                    e.to_string().contains(&format!(
                        "Unknown Account id {TEST_ACCOUNT_ID} on version control {TEST_VERSION_CONTROL}"
                    ))
                });
        }

        #[test]
        fn returns_core() {
            let mut deps = mock_dependencies();

            deps.querier = MockQuerierBuilder::default()
                .with_contract_item(TEST_MANAGER, ACCOUNT_ID, &TEST_ACCOUNT_ID)
                .with_contract_map_entry(
                    TEST_VERSION_CONTROL,
                    ACCOUNT_ADDRESSES,
                    (&TEST_ACCOUNT_ID, test_account_base()),
                )
                .build();

            let binding = MockBinding;

            let res = binding
                .account_registry(deps.as_ref())
                .assert_manager(&Addr::unchecked(TEST_MANAGER));

            assert_that!(res).is_ok().is_equal_to(test_account_base());
        }

        #[test]
        fn errors_when_not_manager_of_returned_os() {
            let mut deps = mock_dependencies();

            deps.querier = MockQuerierBuilder::default()
                .with_contract_item(TEST_MANAGER, ACCOUNT_ID, &TEST_ACCOUNT_ID)
                .with_contract_map_entry(
                    TEST_VERSION_CONTROL,
                    ACCOUNT_ADDRESSES,
                    (
                        &TEST_ACCOUNT_ID,
                        AccountBase {
                            manager: Addr::unchecked("not_manager"),
                            proxy: Addr::unchecked(TEST_PROXY),
                        },
                    ),
                )
                .build();

            let binding = MockBinding;

            let res = binding
                .account_registry(deps.as_ref())
                .assert_manager(&Addr::unchecked(TEST_MANAGER));

            assert_that!(res)
                .is_err()
                .matches(|e| matches!(e, AbstractSdkError::NotManager(..)))
                .matches(|e| e.to_string().contains("not the Manager"))
                .matches(|e| e.to_string().contains(TEST_MANAGER));
        }
    }
}