abstract_interface/
ibc.rs

1use crate::{AbstractInterfaceError, IbcClient, IbcHost, Registry};
2use abstract_std::{IBC_CLIENT, IBC_HOST};
3use cw_orch::prelude::*;
4
5#[derive(Clone)]
6pub struct AbstractIbc<Chain: CwEnv> {
7    pub client: IbcClient<Chain>,
8    pub host: IbcHost<Chain>,
9}
10
11impl<Chain: CwEnv> AbstractIbc<Chain> {
12    pub fn new(chain: &Chain) -> Self {
13        let ibc_client = IbcClient::new(IBC_CLIENT, chain.clone());
14        let ibc_host = IbcHost::new(IBC_HOST, chain.clone());
15        Self {
16            client: ibc_client,
17            host: ibc_host,
18        }
19    }
20
21    pub fn upload(&self) -> Result<(), crate::AbstractInterfaceError> {
22        self.client.upload()?;
23        self.host.upload()?;
24        Ok(())
25    }
26
27    pub fn instantiate(&self, admin: &Addr) -> Result<(), CwOrchError> {
28        self.client.instantiate(
29            &abstract_std::ibc_client::InstantiateMsg {},
30            Some(admin),
31            &[],
32        )?;
33
34        self.host
35            .instantiate(&abstract_std::ibc_host::InstantiateMsg {}, Some(admin), &[])?;
36        Ok(())
37    }
38
39    pub fn register(&self, registry: &Registry<Chain>) -> Result<(), AbstractInterfaceError> {
40        registry.register_natives(vec![
41            (
42                self.client.as_instance(),
43                ibc_client::contract::CONTRACT_VERSION.to_string(),
44            ),
45            (
46                self.host.as_instance(),
47                ibc_host::contract::CONTRACT_VERSION.to_string(),
48            ),
49        ])
50    }
51
52    pub fn call_as(&self, sender: &<Chain as TxHandler>::Sender) -> Self {
53        Self {
54            client: self.client.call_as(sender),
55            host: self.host.call_as(sender),
56        }
57    }
58}
59
60#[cfg(feature = "interchain")]
61// Helpers to create connection with another chain
62pub mod connection {
63    use crate::Abstract;
64
65    use super::*;
66    use abstract_std::ibc_client::{ExecuteMsgFns, QueryMsgFns};
67    use abstract_std::ibc_host::ExecuteMsgFns as _;
68    use abstract_std::objects::TruncatedChainId;
69    use cw_orch::environment::Environment;
70    use cw_orch_interchain::prelude::*;
71    use cw_orch_polytone::interchain::PolytoneConnection;
72
73    impl<Chain: IbcQueryHandler> Abstract<Chain> {
74        /// This is used for creating a testing connection between two Abstract connections.
75        ///
76        /// If a polytone deployment is already , it uses the existing deployment, If it doesn't exist, it creates it
77        ///
78        /// You usually don't need this function on actual networks if you're not an Abstract maintainer
79        pub fn connect_to<IBC: InterchainEnv<Chain>>(
80            &self,
81            remote_abstr: &Abstract<Chain>,
82            interchain: &IBC,
83        ) -> Result<(), AbstractInterfaceError> {
84            connect_one_way_to(self, remote_abstr, interchain)?;
85            connect_one_way_to(remote_abstr, self, interchain)?;
86            Ok(())
87        }
88
89        /// This is used for completely removing a connection between two Abstract connections.
90        pub fn disconnect_from(
91            &self,
92            remote_abstr: &Abstract<Chain>,
93        ) -> Result<(), AbstractInterfaceError> {
94            disconnect_one_way_from(self, remote_abstr)?;
95            disconnect_one_way_from(remote_abstr, self)?;
96            Ok(())
97        }
98    }
99
100    impl AbstractIbc<Daemon> {
101        /// Register infrastructure on connected chains
102        pub fn register_infrastructure(&self) -> Result<(), AbstractInterfaceError> {
103            let register_infrastructures =
104                connection::list_ibc_infrastructures(self.host.environment().clone());
105
106            for (chain, ibc_infrastructure) in register_infrastructures.counterparts {
107                use abstract_std::ibc_client::ExecuteMsgFns;
108
109                self.client.register_infrastructure(
110                    chain,
111                    ibc_infrastructure.remote_abstract_host,
112                    ibc_infrastructure.polytone_note,
113                )?;
114            }
115            Ok(())
116        }
117    }
118
119    pub fn connect_one_way_to<Chain: IbcQueryHandler, IBC: InterchainEnv<Chain>>(
120        abstr_client: &Abstract<Chain>,
121        abstr_host: &Abstract<Chain>,
122        interchain: &IBC,
123    ) -> Result<(), AbstractInterfaceError> {
124        // First we register client and host respectively
125        let chain1_id = abstr_client.ibc.client.environment().chain_id();
126        let chain1_name = TruncatedChainId::from_chain_id(&chain1_id);
127
128        let chain2_id = abstr_host.ibc.client.environment().chain_id();
129        let chain2_name = TruncatedChainId::from_chain_id(&chain2_id);
130
131        // We get the polytone connection
132        let polytone_connection =
133            PolytoneConnection::deploy_between_if_needed(interchain, &chain1_id, &chain2_id)?;
134
135        // First, we register the host with the client.
136        // We register the polytone note with it because they are linked
137        // This triggers an IBC message that is used to get back the proxy address
138        let proxy_tx_result = abstr_client.ibc.client.register_infrastructure(
139            chain2_name.clone(),
140            abstr_host.ibc.host.address()?.to_string(),
141            polytone_connection.note.address()?.to_string(),
142        )?;
143        // We make sure the IBC execution is done so that the proxy address is saved inside the Abstract contract
144        interchain.await_and_check_packets(&chain1_id, proxy_tx_result)?;
145
146        // Finally, we get the proxy address and register the proxy with the ibc host for the host chain
147        let proxy_address = abstr_client.ibc.client.host(chain2_name)?;
148
149        abstr_host
150            .ibc
151            .host
152            .register_chain_proxy(chain1_name, proxy_address.remote_polytone_proxy.unwrap())?;
153
154        Ok(())
155    }
156
157    pub fn disconnect_one_way_from<Chain: IbcQueryHandler>(
158        abstr_client: &Abstract<Chain>,
159        abstr_host: &Abstract<Chain>,
160    ) -> Result<(), AbstractInterfaceError> {
161        // First we get the chain names to remove them from host and client
162        let chain1_id = abstr_client.ibc.client.environment().chain_id();
163        let chain1_name = TruncatedChainId::from_chain_id(&chain1_id);
164
165        let chain2_id = abstr_host.ibc.client.environment().chain_id();
166        let chain2_name = TruncatedChainId::from_chain_id(&chain2_id);
167
168        // First, we remove on the client
169        abstr_client.ibc.client.remove_host(chain2_name.clone())?;
170
171        // Then we remove on the host
172        abstr_host.ibc.host.remove_chain_proxy(chain1_name)?;
173
174        Ok(())
175    }
176
177    pub fn list_ibc_infrastructures<Chain: CwEnv>(
178        chain: Chain,
179    ) -> abstract_std::ibc_client::ListIbcInfrastructureResponse {
180        let Ok(polytone) = cw_orch_polytone::Polytone::load_from(chain) else {
181            return abstract_std::ibc_client::ListIbcInfrastructureResponse {
182                counterparts: vec![],
183            };
184        };
185        let abstract_state = crate::AbstractDaemonState::default();
186
187        let mut counterparts = vec![];
188        for connected_polytone in polytone.connected_polytones() {
189            if let Some(remote_abstract_host) =
190                abstract_state.contract_addr(&connected_polytone.chain_id, IBC_HOST)
191            {
192                let truncated_chain_id = abstract_std::objects::TruncatedChainId::from_chain_id(
193                    &connected_polytone.chain_id,
194                );
195                counterparts.push((
196                    truncated_chain_id,
197                    abstract_std::ibc_client::state::IbcInfrastructure {
198                        polytone_note: connected_polytone.note,
199                        remote_abstract_host: remote_abstract_host.into(),
200                        remote_proxy: None,
201                    },
202                ))
203            }
204        }
205        abstract_std::ibc_client::ListIbcInfrastructureResponse { counterparts }
206    }
207}
208
209#[cfg(feature = "interchain")]
210#[cfg(test)]
211mod test {
212    use super::connection::*;
213    use super::*;
214
215    // From https://github.com/CosmosContracts/juno/blob/32568dba828ff7783aea8cb5bb4b8b5832888255/docker/test-user.env#L2
216    const LOCAL_MNEMONIC: &str = "clip hire initial neck maid actor venue client foam budget lock catalog sweet steak waste crater broccoli pipe steak sister coyote moment obvious choose";
217
218    #[test]
219    fn list_ibc() {
220        use networks::JUNO_1;
221
222        // Deploy requires CwEnv, even for load
223        let juno = DaemonBuilder::new(JUNO_1)
224            .mnemonic(LOCAL_MNEMONIC)
225            .build()
226            .unwrap();
227        let l = list_ibc_infrastructures(juno);
228        l.counterparts.iter().any(|(chain_id, ibc_infra)| {
229            chain_id.as_str() == "osmosis"
230                && ibc_infra.remote_abstract_host.starts_with("osmo")
231                && ibc_infra.polytone_note.to_string().starts_with("juno")
232        });
233    }
234}