use abstract_std::{
account::ExecuteMsg,
account::ModuleInstallConfig,
base,
ibc::{Callback, ModuleQuery},
ibc_client::{self, ExecuteMsg as IbcClientMsg, InstalledModuleIdentification},
ibc_host::HostAction,
objects::{module::ModuleInfo, TruncatedChainId},
ABSTRACT_VERSION, IBC_CLIENT,
};
use cosmwasm_std::{
to_json_binary, wasm_execute, Addr, Coin, CosmosMsg, Deps, Empty, Env, QueryRequest,
};
use serde::Serialize;
use super::AbstractApi;
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>, env: &'a Env) -> IbcClient<Self> {
IbcClient {
base: self,
deps,
env,
}
}
}
impl<T> IbcInterface for T where
T: AccountIdentification + ModuleRegistryInterface + ModuleIdentification + ModuleInterface
{
}
impl<'a, T: IbcInterface> AbstractApi<T> for IbcClient<'a, T> {
const API_ID: &'static str = "IbcClient";
fn base(&self) -> &T {
self.base
}
fn deps(&self) -> Deps {
self.deps
}
}
#[derive(Clone)]
pub struct IbcClient<'a, T: IbcInterface> {
base: &'a T,
deps: Deps<'a>,
env: &'a Env,
}
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, self.env)?
.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_account_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 execute(
&self,
msg: &abstract_std::ibc_client::ExecuteMsg,
funds: Vec<Coin>,
) -> AbstractSdkResult<CosmosMsg> {
let wasm_msg = wasm_execute(
self.base.account(self.deps)?.into_addr().into_string(),
&ExecuteMsg::ExecuteOnModule::<Empty> {
module_id: IBC_CLIENT.to_owned(),
exec_msg: to_json_binary(&msg)?,
funds,
},
vec![],
)?;
Ok(wasm_msg.into())
}
pub fn create_remote_account(
&self,
host_chain: TruncatedChainId,
) -> AbstractSdkResult<CosmosMsg> {
self.execute(
&abstract_std::ibc_client::ExecuteMsg::Register {
host_chain,
namespace: None,
install_modules: vec![],
},
vec![],
)
}
pub fn host_action(
&self,
host_chain: TruncatedChainId,
action: HostAction,
) -> AbstractSdkResult<CosmosMsg> {
self.execute(&IbcClientMsg::RemoteAction { host_chain, action }, vec![])
}
pub fn ics20_transfer(
&self,
host_chain: TruncatedChainId,
funds: Vec<Coin>,
memo: Option<String>,
receiver: Option<String>,
) -> AbstractSdkResult<CosmosMsg> {
self.execute(
&IbcClientMsg::SendFunds {
host_chain,
memo,
receiver,
},
funds,
)
}
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 {
account_msgs: vec![abstract_std::account::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 {
account_msgs: vec![abstract_std::account::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 {
account_msgs: vec![abstract_std::account::ExecuteMsg::ExecuteOnModule {
module_id,
exec_msg: to_json_binary(exec_msg)?,
funds: vec![],
}],
},
)
}
pub fn remote_account(
&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 {
#![allow(clippy::needless_borrows_for_generic_args)]
use abstract_testing::prelude::*;
use cosmwasm_std::*;
use super::*;
use crate::{apis::traits::test::abstract_api_test, mock_module::*};
const TEST_HOST_CHAIN: &str = "hostchain";
#[coverage_helper::test]
fn test_host_action_no_callback() {
let (deps, _, stub) = mock_module_setup();
let env = mock_env_validated(deps.api);
let client = stub.ibc_client(deps.as_ref(), &env);
let msg = client.host_action(
TEST_HOST_CHAIN.parse().unwrap(),
HostAction::Dispatch {
account_msgs: vec![abstract_std::account::ExecuteMsg::UpdateStatus {
is_suspended: None,
}],
},
);
assert!(msg.is_ok());
let base = test_account(deps.api);
let expected = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: base.addr().to_string(),
msg: to_json_binary(&ExecuteMsg::ExecuteOnModule::<cosmwasm_std::Empty> {
module_id: IBC_CLIENT.to_owned(),
exec_msg: to_json_binary(&IbcClientMsg::RemoteAction {
host_chain: TEST_HOST_CHAIN.parse().unwrap(),
action: HostAction::Dispatch {
account_msgs: vec![abstract_std::account::ExecuteMsg::UpdateStatus {
is_suspended: None,
}],
},
})
.unwrap(),
funds: vec![],
})
.unwrap(),
funds: vec![],
});
assert_eq!(msg, Ok(expected));
}
#[coverage_helper::test]
fn test_ics20_transfer() {
let (deps, _, stub) = mock_module_setup();
let env = mock_env_validated(deps.api);
let client = stub.ibc_client(deps.as_ref(), &env);
let expected_funds = coins(100, "denom");
let msg = client.ics20_transfer(
TEST_HOST_CHAIN.parse().unwrap(),
expected_funds.clone(),
None,
None,
);
assert!(msg.is_ok());
let base = test_account(deps.api);
let expected = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: base.addr().to_string(),
msg: to_json_binary(&ExecuteMsg::ExecuteOnModule::<cosmwasm_std::Empty> {
module_id: IBC_CLIENT.to_owned(),
exec_msg: to_json_binary(&IbcClientMsg::SendFunds {
host_chain: TEST_HOST_CHAIN.parse().unwrap(),
memo: None,
receiver: None,
})
.unwrap(),
funds: expected_funds,
})
.unwrap(),
funds: vec![],
});
assert_eq!(msg, Ok(expected));
}
#[coverage_helper::test]
fn abstract_api() {
let (deps, _, app) = mock_module_setup();
let env = mock_env_validated(deps.api);
let client = app.ibc_client(deps.as_ref(), &env);
abstract_api_test(client);
}
}