abstract-version-control 0.23.0

Abstract Version Control Contract
Documentation
use abstract_macros::abstract_response;
use abstract_sdk::{execute_update_ownership, query_ownership};
pub(crate) use abstract_std::objects::namespace::ABSTRACT_NAMESPACE;
use abstract_std::{
    objects::namespace::Namespace,
    version_control::{state::NAMESPACES_INFO, Config},
};
use abstract_std::{
    objects::ABSTRACT_ACCOUNT_ID,
    version_control::{state::CONFIG, ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg},
    VERSION_CONTROL,
};
use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response};

use crate::{commands::*, error::VCError, queries};

pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

pub type VCResult<T = Response> = Result<T, VCError>;

#[abstract_response(VERSION_CONTROL)]
pub struct VcResponse;

#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
pub fn instantiate(deps: DepsMut, _env: Env, _info: MessageInfo, msg: InstantiateMsg) -> VCResult {
    cw2::set_contract_version(deps.storage, VERSION_CONTROL, CONTRACT_VERSION)?;

    let InstantiateMsg {
        admin,
        security_disabled,
        namespace_registration_fee,
    } = msg;

    CONFIG.save(
        deps.storage,
        &Config {
            // Account factory should be set by `update_config`
            account_factory_address: None,
            security_disabled: security_disabled.unwrap_or(false),
            namespace_registration_fee,
        },
    )?;

    // Set up the admin
    cw_ownable::initialize_owner(deps.storage, deps.api, Some(&admin))?;

    // Save the abstract namespace to the Abstract admin account
    NAMESPACES_INFO.save(
        deps.storage,
        &Namespace::new(ABSTRACT_NAMESPACE)?,
        &ABSTRACT_ACCOUNT_ID,
    )?;

    Ok(VcResponse::action("instantiate"))
}

#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> VCResult {
    match msg {
        ExecuteMsg::ProposeModules { modules } => propose_modules(deps, info, modules),
        ExecuteMsg::ApproveOrRejectModules { approves, rejects } => {
            approve_or_reject_modules(deps, info, approves, rejects)
        }
        ExecuteMsg::RemoveModule { module } => remove_module(deps, info, module),
        ExecuteMsg::YankModule { module } => yank_module(deps, info, module),
        ExecuteMsg::UpdateModuleConfiguration {
            module_name,
            namespace,
            update_module,
        } => update_module_config(deps, info, module_name, namespace, update_module),
        ExecuteMsg::ClaimNamespace {
            namespace,
            account_id,
        } => claim_namespace(deps, info, account_id, namespace),
        ExecuteMsg::RemoveNamespaces { namespaces } => remove_namespaces(deps, info, namespaces),
        ExecuteMsg::AddAccount {
            account_id,
            account_base: base,
            namespace,
        } => add_account(deps, info, account_id, base, namespace),
        ExecuteMsg::UpdateConfig {
            account_factory_address,
            security_disabled,
            namespace_registration_fee,
        } => update_config(
            deps,
            info,
            account_factory_address,
            security_disabled,
            namespace_registration_fee,
        ),
        ExecuteMsg::UpdateOwnership(action) => {
            execute_update_ownership!(VcResponse, deps, env, info, action)
        }
    }
}

#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> VCResult<Binary> {
    match msg {
        QueryMsg::AccountBase { account_id } => {
            to_json_binary(&queries::handle_account_address_query(deps, account_id)?)
        }
        QueryMsg::Modules { infos } => to_json_binary(&queries::handle_modules_query(deps, infos)?),
        QueryMsg::Namespaces { accounts } => {
            to_json_binary(&queries::handle_namespaces_query(deps, accounts)?)
        }
        QueryMsg::Namespace { namespace } => {
            to_json_binary(&queries::handle_namespace_query(deps, namespace)?)
        }
        QueryMsg::Config {} => {
            let config = CONFIG.load(deps.storage)?;
            to_json_binary(&ConfigResponse {
                account_factory_address: config.account_factory_address,
                security_disabled: config.security_disabled,
                namespace_registration_fee: config.namespace_registration_fee,
            })
        }
        QueryMsg::ModuleList {
            filter,
            start_after,
            limit,
        } => to_json_binary(&queries::handle_module_list_query(
            deps,
            start_after,
            limit,
            filter,
        )?),
        QueryMsg::NamespaceList { start_after, limit } => {
            let start_after = start_after.map(Namespace::try_from).transpose()?;
            to_json_binary(&queries::handle_namespace_list_query(
                deps,
                start_after,
                limit,
            )?)
        }
        QueryMsg::Ownership {} => query_ownership!(deps),
    }
    .map_err(Into::into)
}

#[cfg(test)]
mod tests {
    use abstract_std::objects::ABSTRACT_ACCOUNT_ID;
    use cosmwasm_std::testing::*;
    use speculoos::prelude::*;

    use super::*;
    use crate::testing::*;

    mod instantiate {
        use super::*;

        #[test]
        fn sets_abstract_namespace() -> VCResult<()> {
            let mut deps = mock_dependencies();
            mock_init(deps.as_mut())?;

            let account_id = NAMESPACES_INFO.load(
                deps.as_ref().storage,
                &Namespace::try_from(ABSTRACT_NAMESPACE)?,
            )?;
            assert_that!(account_id).is_equal_to(ABSTRACT_ACCOUNT_ID);

            Ok(())
        }
    }

    mod migrate {
        use abstract_std::{version_control::MigrateMsg, AbstractError};
        use cw_semver::Version;

        use super::*;

        #[test]
        fn disallow_same_version() -> VCResult<()> {
            let mut deps = mock_dependencies();
            mock_old_init(deps.as_mut())?;

            let version: Version = CONTRACT_VERSION.parse().unwrap();

            let res = crate::migrate::migrate(deps.as_mut(), mock_env(), MigrateMsg {});

            assert_that!(res).is_err().is_equal_to(VCError::Abstract(
                AbstractError::CannotDowngradeContract {
                    contract: VERSION_CONTROL.to_string(),
                    from: version.to_string().parse().unwrap(),
                    to: version.to_string().parse().unwrap(),
                },
            ));

            Ok(())
        }

        #[test]
        fn disallow_downgrade() -> VCResult<()> {
            let mut deps = mock_dependencies();
            mock_old_init(deps.as_mut())?;

            let big_version = "999.999.999";
            cw2::set_contract_version(deps.as_mut().storage, VERSION_CONTROL, big_version)?;

            let version: Version = CONTRACT_VERSION.parse().unwrap();

            let res = crate::migrate::migrate(deps.as_mut(), mock_env(), MigrateMsg {});

            assert_that!(res).is_err().is_equal_to(VCError::Abstract(
                AbstractError::CannotDowngradeContract {
                    contract: VERSION_CONTROL.to_string(),
                    from: big_version.parse().unwrap(),
                    to: version.to_string().parse().unwrap(),
                },
            ));

            Ok(())
        }

        #[test]
        fn disallow_name_change() -> VCResult<()> {
            let mut deps = mock_dependencies();
            mock_old_init(deps.as_mut())?;

            let old_version = "0.0.0";
            let old_name = "old:contract";
            cw2::set_contract_version(deps.as_mut().storage, old_name, old_version)?;

            let res = crate::migrate::migrate(deps.as_mut(), mock_env(), MigrateMsg {});

            assert_that!(res).is_err().is_equal_to(VCError::Abstract(
                AbstractError::ContractNameMismatch {
                    from: old_name.to_string(),
                    to: VERSION_CONTROL.to_string(),
                },
            ));

            Ok(())
        }

        #[test]
        fn works() -> VCResult<()> {
            let mut deps = mock_dependencies();
            mock_old_init(deps.as_mut())?;

            let version: Version = CONTRACT_VERSION.parse().unwrap();

            let small_version = Version {
                minor: version.minor - 1,
                ..version.clone()
            }
            .to_string();
            cw2::set_contract_version(deps.as_mut().storage, VERSION_CONTROL, small_version)?;

            let res = crate::migrate::migrate(deps.as_mut(), mock_env(), MigrateMsg {})?;
            assert_that!(res.messages).has_length(0);

            assert_that!(cw2::get_contract_version(&deps.storage)?.version)
                .is_equal_to(version.to_string());
            Ok(())
        }
    }
}