use abstract_std::{
ibc::CallbackInfo,
ibc_client::{self, ExecuteMsg as IbcClientMsg},
ibc_host::HostAction,
manager::ModuleInstallConfig,
objects::module::{ModuleInfo, ModuleVersion},
proxy::ExecuteMsg,
IBC_CLIENT,
};
use cosmwasm_std::{
to_json_binary, wasm_execute, Addr, Coin, CosmosMsg, Deps, Empty, QueryRequest,
};
use serde::Serialize;
use super::{AbstractApi, ApiIdentification};
use crate::{
features::{AccountIdentification, ModuleIdentification},
AbstractSdkResult, ModuleRegistryInterface,
};
pub trait IbcInterface:
AccountIdentification + ModuleRegistryInterface + ModuleIdentification
{
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
{
}
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> {
self.base
.module_registry(self.deps)?
.query_module(ModuleInfo::from_id_latest(IBC_CLIENT)?)?
.reference
.unwrap_native()
.map_err(Into::into)
}
pub fn register_ibc_client(&self) -> AbstractSdkResult<CosmosMsg> {
Ok(wasm_execute(
self.base.manager_address(self.deps)?,
&abstract_std::manager::ExecuteMsg::InstallModules {
modules: vec![ModuleInstallConfig::new(
ModuleInfo::from_id(IBC_CLIENT, ModuleVersion::Latest)?,
None,
)],
},
vec![],
)?
.into())
}
pub fn create_remote_account(
&self,
host_chain: String,
) -> 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 install_remote_app<M: Serialize>(
&self,
host_chain: String,
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: String,
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: String,
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 module_ibc_action<M: Serialize>(
&self,
host_chain: String,
target_module: ModuleInfo,
exec_msg: &M,
callback_info: Option<CallbackInfo>,
) -> 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_info,
},
vec![],
)?;
Ok(msg.into())
}
pub fn ibc_query(
&self,
host_chain: String,
query_msg: impl Into<QueryRequest<Empty>>,
callback_info: CallbackInfo,
) -> AbstractSdkResult<CosmosMsg> {
let ibc_client_addr = self.module_address()?;
let msg = wasm_execute(
ibc_client_addr,
&ibc_client::ExecuteMsg::IbcQuery {
host_chain,
query: query_msg.into(),
callback_info,
},
vec![],
)?;
Ok(msg.into())
}
pub fn host_action(
&self,
host_chain: String,
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: String,
funds: Vec<Coin>,
) -> AbstractSdkResult<CosmosMsg> {
Ok(wasm_execute(
self.base.proxy_address(self.deps)?.to_string(),
&ExecuteMsg::IbcAction {
msg: IbcClientMsg::SendFunds { host_chain, funds },
},
vec![],
)?
.into())
}
pub fn remote_proxy_addr(&self, host_chain: &str) -> 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.parse()?),
)
.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.into(),
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.into(),
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.to_string(), expected_funds.clone());
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.into(),
funds: expected_funds,
},
})
.unwrap(),
funds: vec![],
});
assert_that!(msg.unwrap()).is_equal_to::<CosmosMsg>(expected);
}
}