abstract_std/native/ibc/
ibc_client.rs

1use cosmwasm_schema::QueryResponses;
2use cosmwasm_std::{Addr, Binary, CosmosMsg, Deps, Empty, QueryRequest, StdError, Uint64};
3
4use self::state::IbcInfrastructure;
5use crate::{
6    account::{self, ModuleInstallConfig},
7    ibc::{Callback, ModuleQuery},
8    ibc_host::HostAction,
9    objects::{
10        account::AccountId, module::ModuleInfo, module_reference::ModuleReference,
11        registry::RegistryContract, TruncatedChainId,
12    },
13    AbstractError,
14};
15
16use super::{polytone_callbacks, IBCLifecycleComplete};
17
18pub mod state {
19
20    use cosmwasm_std::{Addr, Binary, Coin};
21    use cw_storage_plus::{Item, Map};
22
23    use crate::{
24        ibc::ICS20PacketIdentifier,
25        objects::{
26            account::{AccountSequence, AccountTrace},
27            storage_namespaces, TruncatedChainId,
28        },
29    };
30
31    /// Information about the deployed infrastructure we're connected to.
32    #[cosmwasm_schema::cw_serde]
33    pub struct IbcInfrastructure {
34        /// Address of the polytone note deployed on the local chain. This contract will forward the messages for us.
35        pub polytone_note: Addr,
36        /// The address of the abstract host deployed on the remote chain. This address will be called with our packet.
37        pub remote_abstract_host: String,
38        // The remote polytone proxy address which will be called by the polytone host.
39        pub remote_proxy: Option<String>,
40    }
41
42    #[cosmwasm_schema::cw_serde]
43    pub struct AccountCallbackPayload {
44        pub channel_id: String,
45        pub account_address: Addr,
46        /// Funds sent initially
47        pub funds: Coin,
48        /// Base64 encoded `account::ExecuteMsg` to allow callback different versions of the accounts
49        pub msgs: Vec<Binary>,
50    }
51
52    // Saves the local note deployed contract and the remote abstract host connected
53    // This allows sending cross-chain messages
54    pub const IBC_INFRA: Map<&TruncatedChainId, IbcInfrastructure> =
55        Map::new(storage_namespaces::ibc_client::IBC_INFRA);
56    pub const REVERSE_POLYTONE_NOTE: Map<&Addr, TruncatedChainId> =
57        Map::new(storage_namespaces::ibc_client::REVERSE_POLYTONE_NOTE);
58
59    /// (account_trace, account_sequence, chain_name) -> remote account address. We use a
60    /// triple instead of including AccountId since nested tuples do not behave as expected due to
61    /// a bug that will be fixed in a future release.
62    pub const ACCOUNTS: Map<(&AccountTrace, AccountSequence, &TruncatedChainId), String> =
63        Map::new(storage_namespaces::ibc_client::ACCOUNTS);
64
65    // For callbacks tests
66    pub const ACKS: Item<Vec<String>> = Item::new(storage_namespaces::ibc_client::ACKS);
67    pub const ICS20_ACCOUNT_CALLBACKS: Map<ICS20PacketIdentifier, (Addr, Coin, Vec<Binary>)> =
68        Map::new(storage_namespaces::ibc_client::ICS20_ACCOUNT_CALLBACKS);
69    pub const ICS20_ACCOUNT_CALLBACK_PAYLOAD: Item<AccountCallbackPayload> =
70        Item::new(storage_namespaces::ibc_client::ICS20_ACCOUNT_CALLBACK_PAYLOAD);
71}
72
73/// This needs no info. Owner of the contract is whoever signed the InstantiateMsg.
74#[cosmwasm_schema::cw_serde]
75pub struct InstantiateMsg {}
76
77#[cosmwasm_schema::cw_serde]
78pub struct MigrateMsg {}
79
80#[cosmwasm_schema::cw_serde]
81#[derive(cw_orch::ExecuteFns)]
82pub enum ExecuteMsg {
83    /// Update the ownership.
84    UpdateOwnership(cw_ownable::Action),
85    /// Owner method: Registers the polytone note on the local chain as well as the host on the remote chain to send messages through
86    /// This allows for monitoring which chain are connected to the contract remotely
87    RegisterInfrastructure {
88        /// Chain to register the infrastructure for ("juno", "osmosis", etc.)
89        chain: TruncatedChainId,
90        /// Polytone note (locally deployed)
91        note: String,
92        /// Address of the abstract host deployed on the remote chain
93        host: String,
94    },
95    /// Only callable by Account
96    /// Will attempt to forward the specified funds to the corresponding
97    /// address on the remote chain.
98    SendFunds {
99        /// host chain to be executed on
100        /// Example: "osmosis"
101        host_chain: TruncatedChainId,
102        /// Address of the token receiver on host chain
103        /// Defaults to address of the remote account
104        receiver: Option<String>,
105        memo: Option<String>,
106    },
107    /// Only callable by Account
108    /// Will attempt to forward the specified funds to the account
109    /// on the remote chain.
110    SendFundsWithActions {
111        /// host chain to be executed on
112        /// Example: "osmosis"
113        host_chain: TruncatedChainId,
114        /// Actions on the account that will be executed after successful transfer
115        /// Encoded with base64 to allow different versions of the account
116        /// Note: ibc-client have to be whitelisted
117        actions: Vec<Binary>,
118    },
119    /// Only callable by Account
120    /// Register an Account on a remote chain over IBC
121    /// This action creates a account for them on the remote chain.
122    Register {
123        /// host chain to be executed on
124        /// Example: "osmosis"
125        host_chain: TruncatedChainId,
126        namespace: Option<String>,
127        install_modules: Vec<ModuleInstallConfig>,
128    },
129    /// Only callable by Account Module
130    // ANCHOR: module-ibc-action
131    ModuleIbcAction {
132        /// host chain to be executed on
133        /// Example: "osmosis"
134        host_chain: TruncatedChainId,
135        /// Module of this account on host chain
136        target_module: ModuleInfo,
137        /// Json-encoded IbcMsg to the target module
138        msg: Binary,
139        /// Callback info to identify the callback that is sent (acts similar to the reply ID)
140        callback: Option<Callback>,
141    },
142    /// Only callable by Account Module
143    // ANCHOR_END: module-ibc-action
144    IbcQuery {
145        /// host chain to be executed on
146        /// Example: "osmosis"
147        host_chain: TruncatedChainId,
148        /// Cosmos Query requests
149        queries: Vec<QueryRequest<ModuleQuery>>,
150        /// Callback info to identify the callback that is sent (acts similar to the reply ID)
151        callback: Callback,
152    },
153    /// Only callable by Account
154    /// Action on remote ibc host
155    /// Which currently only support account messages
156    RemoteAction {
157        /// host chain to be executed on
158        /// Example: "osmosis"
159        host_chain: TruncatedChainId,
160        /// execute the custom host function
161        action: HostAction,
162    },
163    /// Owner method: Remove connection for remote chain
164    RemoveHost { host_chain: TruncatedChainId },
165    /// Callback from the Polytone implementation
166    /// This is triggered regardless of the execution result
167    Callback(polytone_callbacks::CallbackMessage),
168}
169
170/// Copy of [polytone_note::msg::ExecuteMsg](https://docs.rs/polytone-note/1.0.0/polytone_note/msg/enum.ExecuteMsg.html)
171#[cosmwasm_schema::cw_serde]
172pub enum PolytoneNoteExecuteMsg {
173    /// Performs the requested queries on the voice chain and returns
174    /// a callback of Vec<QuerierResult>, or ACK-FAIL if unmarshalling
175    /// any of the query requests fails.
176    Query {
177        msgs: Vec<QueryRequest<Empty>>,
178        callback: polytone_callbacks::CallbackRequest,
179        timeout_seconds: Uint64,
180    },
181    /// Executes the requested messages on the voice chain on behalf
182    /// of the note chain sender. Message receivers can return data in
183    /// their callbacks by calling `set_data` on their `Response`
184    /// object. Optionally, returns a callback of `Vec<Callback>` where
185    /// index `i` corresponds to the callback for `msgs[i]`.
186    ///
187    /// Accounts are created on the voice chain after the first call
188    /// to execute by the local address. To create an account, but
189    /// perform no additional actions, pass an empty list to
190    /// `msgs`. Accounts are queryable via the `RemoteAddress {
191    /// local_address }` query after they have been created.
192    Execute {
193        msgs: Vec<CosmosMsg<Empty>>,
194        callback: Option<polytone_callbacks::CallbackRequest>,
195        timeout_seconds: Uint64,
196    },
197}
198
199/// This enum is used for sending callbacks to the note contract of the IBC client
200#[cosmwasm_schema::cw_serde]
201pub enum IbcClientCallback {
202    ModuleRemoteAction {
203        sender_address: String,
204        callback: Callback,
205        initiator_msg: Binary,
206    },
207    ModuleRemoteQuery {
208        sender_address: String,
209        callback: Callback,
210        queries: Vec<QueryRequest<ModuleQuery>>,
211    },
212    CreateAccount {
213        account_id: AccountId,
214    },
215    WhoAmI {},
216}
217
218/// This is used for identifying calling modules
219/// For adapters, we don't need the account id because it's independent of an account
220/// For apps and standalone, the account id is used to identify the calling module
221#[cosmwasm_schema::cw_serde]
222pub struct InstalledModuleIdentification {
223    pub module_info: ModuleInfo,
224    pub account_id: Option<AccountId>,
225}
226
227#[cosmwasm_schema::cw_serde]
228pub struct ModuleAddr {
229    pub reference: ModuleReference,
230    pub address: Addr,
231}
232
233impl InstalledModuleIdentification {
234    pub fn addr(
235        &self,
236        deps: Deps,
237        registry: RegistryContract,
238    ) -> Result<ModuleAddr, AbstractError> {
239        let target_module_resolved =
240            registry.query_module(self.module_info.clone(), &deps.querier)?;
241
242        let no_account_id_error =
243            StdError::generic_err("Account id not specified in installed module definition");
244
245        let target_addr = match &target_module_resolved.reference {
246            ModuleReference::Account(code_id) => {
247                let target_account_id = self.account_id.clone().ok_or(no_account_id_error)?;
248                let account = registry.account(&target_account_id, &deps.querier)?;
249
250                if deps
251                    .querier
252                    .query_wasm_contract_info(account.addr().as_str())?
253                    .code_id
254                    == *code_id
255                {
256                    account.into_addr()
257                } else {
258                    Err(StdError::generic_err(
259                        "Account contract doesn't correspond to code id of the account",
260                    ))?
261                }
262            }
263            ModuleReference::Native(addr)
264            | ModuleReference::Adapter(addr)
265            | ModuleReference::Service(addr) => addr.clone(),
266            ModuleReference::App(_) | ModuleReference::Standalone(_) => {
267                let target_account_id = self.account_id.clone().ok_or(no_account_id_error)?;
268                let account = registry.account(&target_account_id, &deps.querier)?;
269
270                let module_info: account::ModuleAddressesResponse = deps.querier.query_wasm_smart(
271                    account.into_addr(),
272                    &account::QueryMsg::ModuleAddresses {
273                        ids: vec![self.module_info.id()],
274                    },
275                )?;
276                module_info
277                    .modules
278                    .first()
279                    .ok_or(AbstractError::AppNotInstalled(self.module_info.to_string()))?
280                    .1
281                    .clone()
282            }
283        };
284        Ok(ModuleAddr {
285            reference: target_module_resolved.reference,
286            address: target_addr,
287        })
288    }
289}
290
291#[cosmwasm_schema::cw_serde]
292#[derive(QueryResponses, cw_orch::QueryFns)]
293pub enum QueryMsg {
294    /// Queries the ownership of the ibc client contract
295    /// Returns [`cw_ownable::Ownership<Addr>`]
296    #[returns(cw_ownable::Ownership<Addr> )]
297    Ownership {},
298
299    /// Returns config
300    /// Returns [`ConfigResponse`]
301    #[returns(ConfigResponse)]
302    Config {},
303
304    /// Returns the host information associated with a specific chain-name (e.g. osmosis, juno)
305    /// Returns [`HostResponse`]
306    #[returns(HostResponse)]
307    Host { chain_name: TruncatedChainId },
308
309    /// Get list of remote accounts
310    /// Returns [`ListAccountsResponse`]
311    #[returns(ListAccountsResponse)]
312    ListAccounts {
313        start: Option<(AccountId, String)>,
314        limit: Option<u32>,
315    },
316
317    /// Get remote account address for one chain
318    /// Returns [`AccountResponse`]
319    #[returns(AccountResponse)]
320    #[cw_orch(fn_name("remote_account"))]
321    Account {
322        chain_name: TruncatedChainId,
323        account_id: AccountId,
324    },
325
326    /// Get the hosts
327    /// Returns [`ListRemoteHostsResponse`]
328    #[returns(ListRemoteHostsResponse)]
329    ListRemoteHosts {},
330
331    /// Get the Polytone execution proxies
332    /// Returns [`ListRemoteProxiesResponse`]
333    #[returns(ListRemoteProxiesResponse)]
334    ListRemoteProxies {},
335
336    /// Get the IBC execution proxies based on the account id passed
337    /// Returns [`ListRemoteAccountsResponse`]
338    #[returns(ListRemoteAccountsResponse)]
339    ListRemoteAccountsByAccountId { account_id: AccountId },
340
341    /// Get the IBC counterparts connected to this abstract ibc client
342    /// Returns [`ListIbcInfrastructureResponse`]
343    #[returns(ListIbcInfrastructureResponse)]
344    ListIbcInfrastructures {},
345}
346
347#[cosmwasm_schema::cw_serde]
348pub enum SudoMsg {
349    /// For IBC hooks acknoledgments
350    #[serde(rename = "ibc_lifecycle_complete")]
351    IBCLifecycleComplete(IBCLifecycleComplete),
352}
353#[cosmwasm_schema::cw_serde]
354pub struct ConfigResponse {
355    pub ans_host: Addr,
356    pub registry_address: Addr,
357}
358
359#[cosmwasm_schema::cw_serde]
360pub struct ListAccountsResponse {
361    pub accounts: Vec<(AccountId, TruncatedChainId, String)>,
362}
363
364#[cosmwasm_schema::cw_serde]
365pub struct ListRemoteHostsResponse {
366    pub hosts: Vec<(TruncatedChainId, String)>,
367}
368
369#[cosmwasm_schema::cw_serde]
370pub struct ListRemoteAccountsResponse {
371    pub accounts: Vec<(TruncatedChainId, Option<String>)>,
372}
373
374pub type ListRemoteProxiesResponse = ListRemoteAccountsResponse;
375
376#[cosmwasm_schema::cw_serde]
377pub struct ListIbcInfrastructureResponse {
378    pub counterparts: Vec<(TruncatedChainId, IbcInfrastructure)>,
379}
380
381#[cosmwasm_schema::cw_serde]
382pub struct HostResponse {
383    pub remote_host: String,
384    pub remote_polytone_proxy: Option<String>,
385}
386
387#[cosmwasm_schema::cw_serde]
388pub struct AccountResponse {
389    pub remote_account_addr: Option<String>,
390}
391
392#[cfg(test)]
393mod tests {
394    use cosmwasm_std::{to_json_binary, CosmosMsg, Empty};
395
396    use crate::app::ExecuteMsg;
397    use crate::ibc::{Callback, IbcResponseMsg, IbcResult};
398
399    // ... (other test functions)
400
401    #[coverage_helper::test]
402    fn test_response_msg_to_callback_msg() {
403        let receiver = "receiver".to_string();
404
405        let result = IbcResult::FatalError("ibc execution error".to_string());
406
407        let response_msg = IbcResponseMsg {
408            callback: Callback::new(&String::from("15")).unwrap(),
409            result,
410        };
411
412        let actual: CosmosMsg<Empty> = response_msg
413            .clone()
414            .into_cosmos_msg(receiver.clone())
415            .unwrap();
416
417        assert_eq!(
418            actual,
419            CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute {
420                contract_addr: receiver,
421                // we can't test the message because the fields in it are private
422                msg: to_json_binary(&ExecuteMsg::<Empty>::IbcCallback(response_msg)).unwrap(),
423                funds: vec![],
424            })
425        )
426    }
427}