use abstract_std::{
base,
ibc::{Callback, ModuleQuery},
ibc_client::{self, ExecuteMsg as IbcClientMsg, InstalledModuleIdentification},
ibc_host::HostAction,
manager::ModuleInstallConfig,
objects::{module::ModuleInfo, TruncatedChainId},
proxy::ExecuteMsg,
ABSTRACT_VERSION, IBC_CLIENT,
};
use cosmwasm_std::{to_json_binary, wasm_execute, Addr, Coin, CosmosMsg, Deps, QueryRequest};
use serde::Serialize;
use super::{AbstractApi, ApiIdentification};
use crate::{
features::{AccountExecutor, AccountIdentification, ModuleIdentification},
AbstractSdkResult, ModuleInterface, ModuleRegistryInterface,
};
pub trait IbcInterface:
AccountIdentification + ModuleRegistryInterface + ModuleIdentification + ModuleInterface
{
fn ibc_client<'a>(&'a self, deps: Deps<'a>) -> IbcClient<Self> {
IbcClient { base: self, deps }
}
}
impl<T> IbcInterface for T where
T: AccountIdentification + ModuleRegistryInterface + ModuleIdentification + ModuleInterface
{
}
impl<'a, T: IbcInterface> AbstractApi<T> for IbcClient<'a, T> {
fn base(&self) -> &T {
self.base
}
fn deps(&self) -> Deps {
self.deps
}
}
impl<'a, T: IbcInterface> ApiIdentification for IbcClient<'a, T> {
fn api_id() -> String {
"IbcClient".to_owned()
}
}
#[derive(Clone)]
pub struct IbcClient<'a, T: IbcInterface> {
base: &'a T,
deps: Deps<'a>,
}
impl<'a, T: IbcInterface> IbcClient<'a, T> {
pub fn module_address(&self) -> AbstractSdkResult<Addr> {
let modules = self.base.modules(self.deps);
modules.assert_module_dependency(IBC_CLIENT)?;
self.base
.module_registry(self.deps)?
.query_module(ModuleInfo::from_id(IBC_CLIENT, ABSTRACT_VERSION.into())?)?
.reference
.unwrap_native()
.map_err(Into::into)
}
pub fn module_ibc_action<M: Serialize>(
&self,
host_chain: TruncatedChainId,
target_module: ModuleInfo,
exec_msg: &M,
callback: Option<Callback>,
) -> AbstractSdkResult<CosmosMsg> {
let ibc_client_addr = self.module_address()?;
let msg = wasm_execute(
ibc_client_addr,
&ibc_client::ExecuteMsg::ModuleIbcAction {
host_chain,
target_module,
msg: to_json_binary(exec_msg)?,
callback,
},
vec![],
)?;
Ok(msg.into())
}
pub fn module_ibc_query<B: Serialize, M: Serialize>(
&self,
host_chain: TruncatedChainId,
target_module: InstalledModuleIdentification,
query_msg: &base::QueryMsg<B, M>,
callback: Callback,
) -> AbstractSdkResult<CosmosMsg> {
let ibc_client_addr = self.module_address()?;
let msg = wasm_execute(
ibc_client_addr,
&ibc_client::ExecuteMsg::IbcQuery {
host_chain,
queries: vec![QueryRequest::Custom(ModuleQuery {
target_module,
msg: to_json_binary(query_msg)?,
})],
callback,
},
vec![],
)?;
Ok(msg.into())
}
pub fn ibc_query(
&self,
host_chain: TruncatedChainId,
query: impl Into<QueryRequest<ModuleQuery>>,
callback: Callback,
) -> AbstractSdkResult<CosmosMsg> {
let ibc_client_addr = self.module_address()?;
let msg = wasm_execute(
ibc_client_addr,
&ibc_client::ExecuteMsg::IbcQuery {
host_chain,
queries: vec![query.into()],
callback,
},
vec![],
)?;
Ok(msg.into())
}
pub fn ibc_queries(
&self,
host_chain: TruncatedChainId,
queries: Vec<QueryRequest<ModuleQuery>>,
callback: Callback,
) -> AbstractSdkResult<CosmosMsg> {
let ibc_client_addr = self.module_address()?;
let msg = wasm_execute(
ibc_client_addr,
&ibc_client::ExecuteMsg::IbcQuery {
host_chain,
queries,
callback,
},
vec![],
)?;
Ok(msg.into())
}
pub fn remote_proxy_addr(
&self,
host_chain: &TruncatedChainId,
) -> AbstractSdkResult<Option<String>> {
let ibc_client_addr = self.module_address()?;
let account_id = self.base.account_id(self.deps)?;
let (trace, sequence) = account_id.decompose();
ibc_client::state::ACCOUNTS
.query(
&self.deps.querier,
ibc_client_addr,
(&trace, sequence, host_chain),
)
.map_err(Into::into)
}
}
impl<'a, T: IbcInterface + AccountExecutor> IbcClient<'a, T> {
pub fn create_remote_account(
&self,
host_chain: TruncatedChainId,
) -> AbstractSdkResult<CosmosMsg> {
Ok(wasm_execute(
self.base.proxy_address(self.deps)?.to_string(),
&ExecuteMsg::IbcAction {
msg: abstract_std::ibc_client::ExecuteMsg::Register {
host_chain,
base_asset: None,
namespace: None,
install_modules: vec![],
},
},
vec![],
)?
.into())
}
pub fn host_action(
&self,
host_chain: TruncatedChainId,
action: HostAction,
) -> AbstractSdkResult<CosmosMsg> {
Ok(wasm_execute(
self.base.proxy_address(self.deps)?.to_string(),
&ExecuteMsg::IbcAction {
msg: IbcClientMsg::RemoteAction { host_chain, action },
},
vec![],
)?
.into())
}
pub fn ics20_transfer(
&self,
host_chain: TruncatedChainId,
funds: Vec<Coin>,
memo: Option<String>,
) -> AbstractSdkResult<CosmosMsg> {
Ok(wasm_execute(
self.base.proxy_address(self.deps)?.to_string(),
&ExecuteMsg::IbcAction {
msg: IbcClientMsg::SendFunds {
host_chain,
funds,
memo,
},
},
vec![],
)?
.into())
}
pub fn install_remote_app<M: Serialize>(
&self,
host_chain: TruncatedChainId,
module: ModuleInfo,
init_msg: &M,
) -> AbstractSdkResult<CosmosMsg> {
self.host_action(
host_chain,
HostAction::Dispatch {
manager_msgs: vec![abstract_std::manager::ExecuteMsg::InstallModules {
modules: vec![ModuleInstallConfig::new(
module,
Some(to_json_binary(&init_msg)?),
)],
}],
},
)
}
pub fn install_remote_api<M: Serialize>(
&self,
host_chain: TruncatedChainId,
module: ModuleInfo,
) -> AbstractSdkResult<CosmosMsg> {
self.host_action(
host_chain,
HostAction::Dispatch {
manager_msgs: vec![abstract_std::manager::ExecuteMsg::InstallModules {
modules: vec![ModuleInstallConfig::new(module, None)],
}],
},
)
}
pub fn execute_on_module<M: Serialize>(
&self,
host_chain: TruncatedChainId,
module_id: String,
exec_msg: &M,
) -> AbstractSdkResult<CosmosMsg> {
self.host_action(
host_chain,
HostAction::Dispatch {
manager_msgs: vec![abstract_std::manager::ExecuteMsg::ExecOnModule {
module_id,
exec_msg: to_json_binary(exec_msg)?,
}],
},
)
}
pub fn remote_proxy(&self, host_chain: &TruncatedChainId) -> AbstractSdkResult<Option<String>> {
let account_id = self.base.account_id(self.deps)?;
let ibc_client_addr = self.module_address()?;
let (trace, sequence) = account_id.decompose();
ibc_client::state::ACCOUNTS
.query(
&self.deps.querier,
ibc_client_addr,
(&trace, sequence, host_chain),
)
.map_err(Into::into)
}
}
#[cfg(test)]
mod test {
use abstract_testing::prelude::*;
use cosmwasm_std::{testing::*, *};
use speculoos::prelude::*;
use super::*;
use crate::mock_module::*;
const TEST_HOST_CHAIN: &str = "hostchain";
#[test]
fn test_host_action_no_callback() {
let deps = mock_dependencies();
let stub = MockModule::new();
let client = stub.ibc_client(deps.as_ref());
let msg = client.host_action(
TEST_HOST_CHAIN.parse().unwrap(),
HostAction::Dispatch {
manager_msgs: vec![abstract_std::manager::ExecuteMsg::UpdateStatus {
is_suspended: None,
}],
},
);
assert_that!(msg).is_ok();
let expected = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: TEST_PROXY.to_string(),
msg: to_json_binary(&ExecuteMsg::IbcAction {
msg: IbcClientMsg::RemoteAction {
host_chain: TEST_HOST_CHAIN.parse().unwrap(),
action: HostAction::Dispatch {
manager_msgs: vec![abstract_std::manager::ExecuteMsg::UpdateStatus {
is_suspended: None,
}],
},
},
})
.unwrap(),
funds: vec![],
});
assert_that!(msg.unwrap()).is_equal_to::<CosmosMsg>(expected);
}
#[test]
fn test_ics20_transfer() {
let deps = mock_dependencies();
let stub = MockModule::new();
let client = stub.ibc_client(deps.as_ref());
let expected_funds = coins(100, "denom");
let msg = client.ics20_transfer(
TEST_HOST_CHAIN.parse().unwrap(),
expected_funds.clone(),
None,
);
assert_that!(msg).is_ok();
let expected = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: TEST_PROXY.to_string(),
msg: to_json_binary(&ExecuteMsg::IbcAction {
msg: IbcClientMsg::SendFunds {
host_chain: TEST_HOST_CHAIN.parse().unwrap(),
funds: expected_funds,
memo: None,
},
})
.unwrap(),
funds: vec![],
});
assert_that!(msg.unwrap()).is_equal_to::<CosmosMsg>(expected);
}
}