abstract_core/native/
ibc_client.rs

1use cosmwasm_schema::QueryResponses;
2use cosmwasm_std::{Addr, Coin, Empty, QueryRequest};
3use polytone::callbacks::CallbackMessage;
4
5use self::state::IbcInfrastructure;
6use crate::{
7    ibc::CallbackInfo,
8    ibc_host::HostAction,
9    manager::ModuleInstallConfig,
10    objects::{account::AccountId, chain_name::ChainName, AssetEntry},
11};
12
13pub mod state {
14
15    use cosmwasm_std::Addr;
16    use cw_storage_plus::{Item, Map};
17
18    use crate::objects::{
19        account::{AccountSequence, AccountTrace},
20        ans_host::AnsHost,
21        chain_name::ChainName,
22        version_control::VersionControlContract,
23    };
24
25    #[cosmwasm_schema::cw_serde]
26    pub struct Config {
27        pub version_control: VersionControlContract,
28        pub ans_host: AnsHost,
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    // Saves the local note deployed contract and the remote abstract host connected
43    // This allows sending cross-chain messages
44    pub const IBC_INFRA: Map<&ChainName, IbcInfrastructure> = Map::new("ibci");
45    pub const REVERSE_POLYTONE_NOTE: Map<&Addr, ChainName> = Map::new("revpn");
46
47    pub const CONFIG: Item<Config> = Item::new("config");
48    /// (account_trace, account_sequence, chain_name) -> remote proxy account address. We use a
49    /// triple instead of including AccountId since nested tuples do not behave as expected due to
50    /// a bug that will be fixed in a future release.
51    pub const ACCOUNTS: Map<(&AccountTrace, AccountSequence, &ChainName), String> =
52        Map::new("accs");
53
54    // For callbacks tests
55    pub const ACKS: Item<Vec<String>> = Item::new("tmpc");
56}
57
58/// This needs no info. Owner of the contract is whoever signed the InstantiateMsg.
59#[cosmwasm_schema::cw_serde]
60pub struct InstantiateMsg {
61    pub ans_host_address: String,
62    pub version_control_address: String,
63}
64
65#[cosmwasm_schema::cw_serde]
66pub struct MigrateMsg {}
67
68#[cosmwasm_schema::cw_serde]
69#[cfg_attr(feature = "interface", derive(cw_orch::ExecuteFns))]
70pub enum ExecuteMsg {
71    /// Update the ownership.
72    UpdateOwnership(cw_ownable::Action),
73    // Registers the polytone note on the local chain as well as the host on the remote chain to send messages through
74    // This allows for monitoring which chain are connected to the contract remotely
75    RegisterInfrastructure {
76        /// Chain to register the infrastructure for ("juno", "osmosis", etc.)
77        chain: String,
78        /// Polytone note (locally deployed)
79        note: String,
80        /// Address of the abstract host deployed on the remote chain
81        host: String,
82    },
83    /// Changes the config
84    UpdateConfig {
85        ans_host: Option<String>,
86        version_control: Option<String>,
87    },
88    /// Only callable by Account proxy
89    /// Will attempt to forward the specified funds to the corresponding
90    /// address on the remote chain.
91    SendFunds {
92        host_chain: String,
93        funds: Vec<Coin>,
94    },
95    /// Register an Account on a remote chain over IBC
96    /// This action creates a proxy for them on the remote chain.
97    Register {
98        host_chain: String,
99        base_asset: Option<AssetEntry>,
100        namespace: Option<String>,
101        install_modules: Vec<ModuleInstallConfig>,
102    },
103    RemoteAction {
104        // host chain to be executed on
105        // Example: "osmosis"
106        host_chain: String,
107        // execute the custom host function
108        action: HostAction,
109        // optional callback info
110        callback_info: Option<CallbackInfo>,
111    },
112    /// Allows to query something on a remote contract and act on that query result
113    /// This has to be an Execute variant for IBC queries
114    RemoteQueries {
115        // host chain to be executed on
116        // Example: "osmosis"
117        host_chain: String,
118        // execute following queries
119        queries: Vec<QueryRequest<Empty>>,
120        // mandatory callback info
121        callback_info: CallbackInfo,
122    },
123    RemoveHost {
124        host_chain: String,
125    },
126
127    /// Callback from the Polytone implementation
128    /// This is only triggered when a contract execution is succesful
129    Callback(CallbackMessage),
130}
131
132/// This enum is used for sending callbacks to the note contract of the IBC client
133#[cosmwasm_schema::cw_serde]
134pub enum IbcClientCallback {
135    UserRemoteAction(CallbackInfo),
136    CreateAccount { account_id: AccountId },
137    WhoAmI {},
138}
139
140#[cosmwasm_schema::cw_serde]
141#[cfg_attr(feature = "interface", derive(cw_orch::QueryFns))]
142#[derive(QueryResponses)]
143pub enum QueryMsg {
144    /// Queries the ownership of the ibc client contract
145    /// Returns [`cw_ownable::Ownership<Addr>`]
146    #[returns(cw_ownable::Ownership<Addr> )]
147    Ownership {},
148
149    /// Returns config
150    /// Returns [`ConfigResponse`]
151    #[returns(ConfigResponse)]
152    Config {},
153
154    /// Returns the host information associated with a specific chain-name (e.g. osmosis, juno)
155    /// Returns [`HostResponse`]
156    #[returns(HostResponse)]
157    Host { chain_name: String },
158
159    // Shows all open channels (incl. remote info)
160    /// Returns [`ListAccountsResponse`]
161    #[returns(ListAccountsResponse)]
162    ListAccounts {
163        start: Option<(AccountId, String)>,
164        limit: Option<u32>,
165    },
166
167    // Get channel info for one chain
168    /// Returns [`AccountResponse`]
169    #[returns(AccountResponse)]
170    Account {
171        chain: String,
172        account_id: AccountId,
173    },
174    // get the hosts
175    /// Returns [`ListRemoteHostsResponse`]
176    #[returns(ListRemoteHostsResponse)]
177    ListRemoteHosts {},
178
179    // get the IBC execution proxies
180    /// Returns [`ListRemoteProxiesResponse`]
181    #[returns(ListRemoteProxiesResponse)]
182    ListRemoteProxies {},
183
184    // get the IBC execution proxies based on the account id passed
185    /// Returns [`ListRemoteProxiesResponse`]
186    #[returns(ListRemoteProxiesResponse)]
187    ListRemoteProxiesByAccountId { account_id: AccountId },
188
189    // get the IBC counterparts connected to this abstract client
190    /// Returns [`ListIbcInfrastructureResponse`]
191    #[returns(ListIbcInfrastructureResponse)]
192    ListIbcInfrastructures {},
193}
194
195#[cosmwasm_schema::cw_serde]
196pub struct ConfigResponse {
197    pub ans_host: String,
198    pub version_control_address: String,
199}
200
201#[cosmwasm_schema::cw_serde]
202pub struct ListAccountsResponse {
203    pub accounts: Vec<(AccountId, ChainName, String)>,
204}
205
206#[cosmwasm_schema::cw_serde]
207pub struct ListRemoteHostsResponse {
208    pub hosts: Vec<(ChainName, String)>,
209}
210
211#[cosmwasm_schema::cw_serde]
212pub struct ListRemoteProxiesResponse {
213    pub proxies: Vec<(ChainName, Option<String>)>,
214}
215
216#[cosmwasm_schema::cw_serde]
217pub struct ListIbcInfrastructureResponse {
218    pub counterparts: Vec<(ChainName, IbcInfrastructure)>,
219}
220
221#[cosmwasm_schema::cw_serde]
222pub struct HostResponse {
223    pub remote_host: String,
224    pub remote_polytone_proxy: Option<String>,
225}
226
227#[cosmwasm_schema::cw_serde]
228pub struct AccountResponse {
229    pub remote_proxy_addr: String,
230}
231
232#[cosmwasm_schema::cw_serde]
233pub struct RemoteProxyResponse {
234    /// last block balance was updated (0 is never)
235    pub channel_id: String,
236    /// address of the remote proxy
237    pub proxy_address: String,
238}
239
240#[cfg(test)]
241mod tests {
242    use cosmwasm_std::{to_json_binary, CosmosMsg, Empty};
243    use polytone::callbacks::Callback;
244    use speculoos::prelude::*;
245
246    use crate::ibc::IbcResponseMsg;
247
248    // ... (other test functions)
249
250    #[test]
251    fn test_response_msg_to_callback_msg() {
252        let receiver = "receiver".to_string();
253        let callback_id = "15".to_string();
254        let callback_msg = to_json_binary("15").unwrap();
255
256        let result = Callback::FatalError("ibc execution error".to_string());
257
258        let response_msg = IbcResponseMsg {
259            id: callback_id,
260            msg: Some(callback_msg),
261            result,
262        };
263
264        let actual: CosmosMsg<Empty> = response_msg.into_cosmos_msg(receiver.clone()).unwrap();
265
266        assert_that!(actual).matches(|e| {
267            matches!(
268                e,
269                CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute {
270                    contract_addr: _receiver,
271                    // we can't test the message because the fields in it are private
272                    msg: _,
273                    funds: _
274                })
275            )
276        });
277    }
278}