use abstract_std::{
manager::ModuleInstallConfig,
objects::{
module::{ModuleInfo, ModuleStatus, ModuleVersion},
TruncatedChainId,
},
version_control::{ExecuteMsgFns, ModuleFilter, QueryMsgFns},
ABSTRACT_EVENT_TYPE, MANAGER, PROXY,
};
use cosmwasm_std::{from_json, to_json_binary};
use cw2::{ContractVersion, CONTRACT};
use cw_semver::{Version, VersionReq};
use crate::{Abstract, AbstractInterfaceError, AccountDetails, AdapterDeployer};
mod manager;
mod proxy;
use std::collections::HashSet;
use abstract_std::{manager::ManagerModuleInfo, objects::AccountId};
use cw_orch::{environment::Environment, prelude::*};
use serde::Serialize;
use speculoos::prelude::*;
pub use self::{manager::*, proxy::*};
use crate::{get_account_contracts, VersionControl};
#[derive(Clone)]
pub struct AbstractAccount<Chain: CwEnv> {
pub manager: Manager<Chain>,
pub proxy: Proxy<Chain>,
}
impl<Chain: CwEnv> AsRef<AbstractAccount<Chain>> for AbstractAccount<Chain> {
fn as_ref(&self) -> &AbstractAccount<Chain> {
self
}
}
impl<Chain: CwEnv> AbstractAccount<Chain> {
pub fn upload(&mut self) -> Result<(), crate::AbstractInterfaceError> {
self.manager.upload()?;
self.proxy.upload()?;
Ok(())
}
}
impl<Chain: CwEnv> AbstractAccount<Chain> {
pub fn new(abstract_deployment: &Abstract<Chain>, account_id: AccountId) -> Self {
let (manager, proxy) =
get_account_contracts(&abstract_deployment.version_control, account_id);
Self { manager, proxy }
}
pub fn register(
&self,
version_control: &VersionControl<Chain>,
) -> Result<(), crate::AbstractInterfaceError> {
version_control.register_base(self)
}
pub fn install_module<TInitMsg: Serialize>(
&self,
module_id: &str,
init_msg: Option<&TInitMsg>,
funds: Option<&[Coin]>,
) -> Result<Chain::Response, crate::AbstractInterfaceError> {
self.manager.install_module(module_id, init_msg, funds)
}
pub fn install_modules(
&self,
modules: Vec<ModuleInstallConfig>,
funds: Option<&[Coin]>,
) -> Result<Chain::Response, crate::AbstractInterfaceError> {
self.manager
.install_modules(modules, funds)
.map_err(Into::into)
}
pub fn install_modules_auto(
&self,
modules: Vec<ModuleInstallConfig>,
) -> Result<Chain::Response, crate::AbstractInterfaceError> {
self.manager.install_modules_auto(modules)
}
pub fn expect_modules(
&self,
module_addrs: Vec<String>,
) -> Result<Vec<ManagerModuleInfo>, crate::AbstractInterfaceError> {
let abstract_std::manager::ModuleInfosResponse {
module_infos: manager_modules,
} = self.manager.module_infos(None, None)?;
let expected_module_addrs = module_addrs
.into_iter()
.map(Addr::unchecked)
.chain(std::iter::once(self.proxy.address()?))
.collect::<HashSet<_>>();
let actual_module_addrs = manager_modules
.iter()
.map(|module_info| module_info.address.clone())
.collect::<HashSet<_>>();
assert_that!(expected_module_addrs).is_equal_to(actual_module_addrs);
Ok(manager_modules)
}
pub fn is_module_installed(
&self,
module_id: &str,
) -> Result<bool, crate::AbstractInterfaceError> {
let module = self.manager.module_info(module_id)?;
Ok(module.is_some())
}
pub fn expect_whitelist(
&self,
whitelisted_addrs: Vec<String>,
) -> Result<Vec<String>, crate::AbstractInterfaceError> {
let expected_whitelisted_addrs = whitelisted_addrs
.into_iter()
.chain(std::iter::once(self.manager.address()?.into_string()))
.collect::<HashSet<_>>();
let abstract_std::proxy::ConfigResponse {
modules: proxy_whitelist,
} = self.proxy.config()?;
let actual_proxy_whitelist = HashSet::from_iter(proxy_whitelist.clone());
assert_eq!(actual_proxy_whitelist, expected_whitelisted_addrs);
Ok(proxy_whitelist)
}
pub fn id(&self) -> Result<AccountId, crate::AbstractInterfaceError> {
Ok(self.manager.config()?.account_id)
}
pub fn install_adapter<CustomInitMsg: Serialize, T: AdapterDeployer<Chain, CustomInitMsg>>(
&self,
adapter: &T,
funds: Option<&[Coin]>,
) -> Result<Addr, crate::AbstractInterfaceError> {
self.install_module_parse_addr::<Empty, _>(adapter, None, funds)
}
pub fn install_app<CustomInitMsg: Serialize, T: ContractInstance<Chain>>(
&self,
app: &T,
custom_init_msg: &CustomInitMsg,
funds: Option<&[Coin]>,
) -> Result<Addr, crate::AbstractInterfaceError> {
self.install_module_parse_addr(app, Some(&custom_init_msg), funds)
}
pub fn install_standalone<CustomInitMsg: Serialize, T: ContractInstance<Chain>>(
&self,
standalone: &T,
custom_init_msg: &CustomInitMsg,
funds: Option<&[Coin]>,
) -> Result<Addr, crate::AbstractInterfaceError> {
self.install_module_parse_addr(standalone, Some(&custom_init_msg), funds)
}
fn install_module_parse_addr<InitMsg: Serialize, T: ContractInstance<Chain>>(
&self,
module: &T,
init_msg: Option<&InitMsg>,
funds: Option<&[Coin]>,
) -> Result<Addr, crate::AbstractInterfaceError> {
let resp = self.install_module(&module.id(), init_msg, funds)?;
let module_address = resp.event_attr_value(ABSTRACT_EVENT_TYPE, "new_modules")?;
let module_address = Addr::unchecked(module_address);
module.set_address(&module_address);
Ok(module_address)
}
pub fn register_remote_account(
&self,
host_chain: TruncatedChainId,
) -> Result<<Chain as cw_orch::prelude::TxHandler>::Response, crate::AbstractInterfaceError>
{
self.manager.register_remote_account(host_chain)
}
pub fn create_remote_account(
&self,
account_details: AccountDetails,
host_chain: TruncatedChainId,
) -> Result<<Chain as cw_orch::prelude::TxHandler>::Response, crate::AbstractInterfaceError>
{
let AccountDetails {
namespace,
base_asset,
install_modules,
name: _,
description: _,
link: _,
account_id: _,
} = account_details;
self.manager.execute_on_module(
abstract_std::PROXY,
abstract_std::proxy::ExecuteMsg::IbcAction {
msg: abstract_std::ibc_client::ExecuteMsg::Register {
host_chain,
base_asset,
namespace,
install_modules,
},
},
)
}
pub fn create_sub_account(
&self,
account_details: AccountDetails,
funds: Option<&[Coin]>,
) -> Result<AbstractAccount<Chain>, crate::AbstractInterfaceError> {
let AccountDetails {
name,
description,
link,
namespace,
base_asset,
install_modules,
account_id,
} = account_details;
let result = self.manager.execute(
&abstract_std::manager::ExecuteMsg::CreateSubAccount {
name,
description,
link,
base_asset,
namespace,
install_modules,
account_id,
},
funds,
)?;
Self::from_tx_response(self.manager.environment(), result)
}
pub(crate) fn from_tx_response(
chain: &Chain,
result: <Chain as TxHandler>::Response,
) -> Result<AbstractAccount<Chain>, crate::AbstractInterfaceError> {
let acc_seq = &result.event_attr_value(ABSTRACT_EVENT_TYPE, "account_sequence")?;
let trace = &result.event_attr_value(ABSTRACT_EVENT_TYPE, "trace")?;
let id = AccountId::new(
acc_seq.parse().unwrap(),
abstract_std::objects::account::AccountTrace::try_from((*trace).as_str())?,
)?;
let manager = Manager::new_from_id(&id, chain.clone());
let proxy = Proxy::new_from_id(&id, chain.clone());
let manager_address = result.event_attr_value(ABSTRACT_EVENT_TYPE, "manager_address")?;
manager.set_address(&Addr::unchecked(manager_address));
let proxy_address = result.event_attr_value(ABSTRACT_EVENT_TYPE, "proxy_address")?;
proxy.set_address(&Addr::unchecked(proxy_address));
Ok(AbstractAccount { manager, proxy })
}
pub fn upload_and_register_if_needed(
&self,
version_control: &VersionControl<Chain>,
) -> Result<bool, AbstractInterfaceError> {
let mut modules_to_register = Vec::with_capacity(2);
if self.manager.upload_if_needed()?.is_some() {
modules_to_register.push((
self.manager.as_instance(),
::manager::contract::CONTRACT_VERSION.to_string(),
));
};
if self.proxy.upload_if_needed()?.is_some() {
modules_to_register.push((
self.proxy.as_instance(),
::proxy::contract::CONTRACT_VERSION.to_string(),
));
};
let migrated = if !modules_to_register.is_empty() {
version_control.register_account_mods(modules_to_register)?;
true
} else {
false
};
Ok(migrated)
}
pub fn upgrade(
&self,
abstract_deployment: &Abstract<Chain>,
) -> Result<bool, AbstractInterfaceError> {
let mut one_migration_was_successful = false;
{
let mut sub_account_ids = vec![];
let mut start_after = None;
loop {
let sub_account_ids_page = self
.manager
.sub_account_ids(None, start_after)?
.sub_accounts;
start_after = sub_account_ids_page.last().cloned();
if sub_account_ids_page.is_empty() {
break;
}
sub_account_ids.extend(sub_account_ids_page);
}
dbg!(&sub_account_ids);
for sub_account_id in sub_account_ids {
let abstract_account =
AbstractAccount::new(abstract_deployment, AccountId::local(sub_account_id));
if abstract_account.upgrade(abstract_deployment)? {
one_migration_was_successful = true;
}
}
}
loop {
if self.upgrade_next_module_version(PROXY)?.is_none() {
break;
}
one_migration_was_successful = true;
}
loop {
if self.upgrade_next_module_version(MANAGER)?.is_none() {
break;
}
one_migration_was_successful = true;
}
Ok(one_migration_was_successful)
}
fn upgrade_next_module_version(
&self,
module_id: &str,
) -> Result<Option<Chain::Response>, AbstractInterfaceError> {
let chain = self.manager.environment().clone();
let current_cw2_module_version: ContractVersion = if module_id == MANAGER {
let current_manager_version = chain
.wasm_querier()
.raw_query(self.manager.address()?, CONTRACT.as_slice().to_vec())
.unwrap();
from_json(current_manager_version)?
} else {
self.manager
.module_versions(vec![module_id.to_string()])?
.versions[0]
.clone()
};
let current_module_version = Version::parse(¤t_cw2_module_version.version)?;
let module = ModuleInfo::from_id(module_id, current_module_version.to_string().into())?;
let abstr = Abstract::load_from(chain.clone())?;
let all_next_module_versions = abstr
.version_control
.module_list(
Some(ModuleFilter {
namespace: Some(module.namespace.to_string()),
name: Some(module.name.clone()),
version: None,
status: Some(ModuleStatus::Registered),
}),
None,
Some(module.clone()),
)?
.modules
.into_iter()
.map(|module| {
let version: Version = module.module.info.version.clone().try_into().unwrap();
version
})
.collect::<Vec<_>>();
let requirement = VersionReq::parse(current_module_version.to_string().as_str())?;
let non_compatible_versions = all_next_module_versions
.iter()
.filter(|version| !requirement.matches(version))
.collect::<Vec<_>>();
let maybe_min_non_compatible_version = non_compatible_versions.iter().min().cloned();
let selected_version = if let Some(min_non_compatible_version) =
maybe_min_non_compatible_version
{
let requirement = VersionReq::parse(min_non_compatible_version.to_string().as_str())?;
non_compatible_versions
.into_iter()
.filter(|version| requirement.matches(version))
.max()
.unwrap()
.clone()
} else {
let possible_version = all_next_module_versions
.into_iter()
.filter(|version| version != ¤t_module_version)
.max();
if possible_version.is_none() {
return Ok(None);
}
possible_version.unwrap()
};
Some(self.manager.upgrade(vec![(
ModuleInfo::from_id(
module_id,
ModuleVersion::Version(selected_version.to_string()),
)?,
Some(to_json_binary(&Empty {})?),
)]))
.transpose()
.map_err(Into::into)
}
pub fn claim_namespace(
&self,
namespace: impl Into<String>,
) -> Result<Chain::Response, AbstractInterfaceError> {
let abstr = Abstract::load_from(self.manager.environment().clone())?;
abstr
.version_control
.claim_namespace(self.id()?, namespace.into())
.map_err(Into::into)
}
}
impl<Chain: CwEnv> std::fmt::Display for AbstractAccount<Chain> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Account manager: {:?} ({:?}) proxy: {:?} ({:?})",
self.manager.id(),
self.manager
.addr_str()
.or_else(|_| Result::<_, CwOrchError>::Ok(String::from("unknown"))),
self.proxy.id(),
self.proxy
.addr_str()
.or_else(|_| Result::<_, CwOrchError>::Ok(String::from("unknown"))),
)
}
}