gate_pkg/
lib.rs

1use cosmwasm_schema::{cw_serde, QueryResponses};
2use cosmwasm_std::{
3    to_binary, Addr, Binary, Coin, CosmosMsg, Empty, QueryRequest, StdError, StdResult, Uint128,
4    WasmMsg,
5};
6use cw20::Cw20ReceiveMsg;
7
8#[cw_serde]
9pub enum ExecuteMsg {
10    /// This msg allow to to gather and collect all `Requests` received during the execution of the binary msg passed.
11    ///
12    /// The gate contract send the Binary msg to the contract specified.
13    ///
14    /// The contract have to handle the `ReceiveGateMsg(GateMsg::CollectRequest)` variant.
15    CollectRequests { to_contract: Addr, msg: Binary },
16
17    /// Send a list of `GateRequests` to a specific chain.
18    SendRequests {
19        requests: Vec<GateRequest>,
20        chain: String,
21        timeout: Option<u64>,
22    },
23
24    /// Register the `Permission` for the contract that execute this msg.
25    SetPermission {
26        permission: Permission,
27        chain: String,
28    },
29
30    /// Register the `Permission` for a specific contract. The sender of the msg has to be the admin of the contract.
31    SetPermissionFromAdmin {
32        contract: Addr,
33        permission: Permission,
34        chain: String,
35    },
36
37    #[cfg(feature = "gate")]
38    SetVoucherPermission {
39        chain: String,
40        local_voucher_contract: Addr,
41        remote_voucher_contract: String,
42    },
43
44    #[cfg(feature = "gate")]
45    RegisterChainAndChannel {
46        chain: String,
47        src_channel: String,
48        base_denom: String,
49    },
50
51    #[cfg(feature = "gate")]
52    Receive(Cw20ReceiveMsg),
53
54    #[cfg(feature = "gate")]
55    IbcHook(IbcHookMsg),
56
57    #[cfg(feature = "gate")]
58    PrivateSendCollectedMsgs,
59
60    #[cfg(feature = "gate")]
61    PrivateRemoteExecuteRequests {
62        requests_infos: Vec<GateRequestsInfo>,
63        native_denom: Option<String>,
64        from_chain: String,
65    },
66
67    #[cfg(feature = "gate")]
68    PrivateRemoteExecuteQuery {
69        queries: Vec<QueryRequest<Empty>>,
70        from_contract: String,
71        callback_msg: Option<Binary>,
72    },
73}
74
75#[cw_serde]
76#[derive(QueryResponses)]
77pub enum QueryMsg {
78    #[returns(Permission)]
79    /// Return the `Permission` registered for a specific contract.
80    Permission { contract: Addr, chain: String },
81    #[returns(ChannelInfo)]
82    /// Return the `ChannelInfo` for a registered chain.
83    ChannelInfo { chain: String },
84    #[returns(Config)]
85    /// Return the `Config`.
86    Config {},
87}
88
89#[cfg(feature = "gate")]
90#[cw_serde]
91pub enum IbcHookMsg {
92    ExecutePendingRequest { channel: String, sequence: u64 },
93}
94
95#[cw_serde]
96pub enum GateMsg {
97    /// Receive msg from a remote chain
98    ReceivedMsg {
99        sender: String,
100        msg: Binary,
101    },
102    RequestFailed {
103        request: GateRequest,
104    },
105    /// Receive the answer of a previously requested query
106    QueryResponse {
107        queries: Vec<GateQueryResponse>,
108        callback_msg: Option<Binary>,
109    },
110    CollectRequests {
111        sender: Addr,
112        msg: Binary,
113    },
114}
115
116#[cfg(feature = "gate")]
117impl GateMsg {
118    /// serializes the message
119    pub fn into_binary(self) -> StdResult<Binary> {
120        let msg = ReceiverExecuteMsg::ReceiveGateMsg(self);
121        to_binary(&msg)
122    }
123
124    /// creates a cosmos_msg sending this struct to the named contract
125    pub fn into_cosmos_msg<T: Into<String>>(
126        self,
127        contract_addr: T,
128        funds: Vec<Coin>,
129    ) -> StdResult<CosmosMsg> {
130        let msg = self.into_binary()?;
131        let execute = WasmMsg::Execute {
132            contract_addr: contract_addr.into(),
133            msg,
134            funds,
135        };
136        Ok(execute.into())
137    }
138}
139
140#[cw_serde]
141pub enum ReceiverExecuteMsg {
142    ReceiveGateMsg(GateMsg),
143}
144
145#[cw_serde]
146pub struct Config {
147    pub controller: Addr,
148    pub default_timeout: u64,
149    pub default_gas_limit: Option<u64>,
150    pub cw20_icg_code_id: u64,
151    pub voucher_contract: Option<String>,
152    pub base_denom: String,
153    /// When a Packet fails on destination chain, the gate on set a `max_gas` for every `RequestFailed` msg.
154    pub max_gas_amount_per_revert: u64,
155}
156
157/// Permission type for contract to receive a `SendMsg` request.
158#[cw_serde]
159pub enum Permission {
160    /// Only from a list of address.
161    Permissioned { addresses: Vec<String> },
162    /// From any address.
163    Permissionless {},
164}
165
166/// List of Request that can be forwarded to the `gate`.
167#[cw_serde]
168pub enum GateRequest {
169    /// Send a msg to a specific contract in a remote chain.
170    /// The contract that should receive has to:
171    /// - Set the `Permission` in the `gate` contract (if Permission::Permissioned, the remote `gate` assert if the contract allow to receive msg from the `sender`);
172    /// - Handle the `ReceiveGateMsg(GateMsg::ReceviedMsg)` on its `ExecuteMsg` variant
173    SendMsg {
174        msg: Binary,
175        to_contract: String,
176        send_native: Option<SendNativeInfo>,
177    },
178
179    /// Perform a list queries in a remote chain.
180    /// Once the result turn back into the chain, the gate send a ExecuteMsg to the asker contract.
181    /// The asker contract must hanlde the `ReceiveGateMsg(GateMsg::QueryResponse)` on its `ExecuteMsg` variant.
182    Query {
183        queries: Vec<QueryRequest<Empty>>,
184        callback_msg: Option<Binary>,
185    },
186}
187impl GateRequest {
188    pub fn send_native(&self) -> Option<SendNativeInfo> {
189        match self {
190            GateRequest::SendMsg {
191                send_native: native_token,
192                ..
193            } => native_token.clone(),
194            GateRequest::Query { .. } => None,
195        }
196    }
197}
198
199#[cw_serde]
200#[non_exhaustive]
201#[cfg(feature = "gate")]
202pub struct GateRequestsInfo {
203    pub requests: Vec<GateRequest>,
204    pub sender: String,
205    pub fee: Option<Coin>,
206    pub send_native: Option<SendNativeInfo>,
207}
208
209#[cfg(feature = "gate")]
210impl GateRequestsInfo {
211    pub fn new(
212        requests: Vec<GateRequest>,
213        sender: String,
214        sended_funds: Vec<Coin>,
215        base_denom: String,
216    ) -> StdResult<GateRequestsInfo> {
217        let mut new_send_native_info: Option<SendNativeInfo> = None;
218
219        // Merge all SendNativeInfo in one and assert path and denom
220        for request in requests.clone() {
221            new_send_native_info = merge_send_native(&new_send_native_info, &request.send_native())?
222        }
223
224        let mut fee: Option<Coin> = None;
225
226        // Based on the received funds, assert the fees and the native
227        for fund in sended_funds {
228            let mut var_amount = fund.amount;
229            if let Some(ref with_native) = new_send_native_info {
230                if with_native.coin.denom == fund.denom {
231                    var_amount -= with_native.coin.amount;
232                }
233            }
234
235            if base_denom.clone() == fund.denom && var_amount > Uint128::zero() {
236                fee = Some(Coin {
237                    denom: base_denom.clone(),
238                    amount: var_amount,
239                });
240                var_amount = Uint128::zero()
241            }
242
243            if !var_amount.is_zero() {
244                return Err(StdError::generic_err(format!(
245                    "Denom {} recevied but not used",
246                    fund.denom
247                )));
248            }
249        }
250        Ok(GateRequestsInfo {
251            requests,
252            sender,
253            fee,
254            send_native: new_send_native_info,
255        })
256    }
257}
258
259/// Information about the send of native token with `Requests`.
260/// `path_middle_forward` allow to use the packet_forwarding in case the native token has to step in one or more intermediary chains.
261///
262/// `channel_id` specify the channel to use to transfer the tokens.
263/// In case a `path_middle_forward` is setted, the `channel_id` is the last channel to use to send the token to the destination chain.
264///
265/// Example:
266/// `A`->`B`->`C`
267/// - `chain_id` is the channel used on chain `B` to send to chain `C`;
268/// - `path_middle_forward` will be:
269///
270/// ```ignore
271/// vec![
272///     PacketPath{
273///         channel_id: "channel-A",       // Channel on chain A used to transfer on chain B
274///         address: "bech32ChainBAddress" // Valid bech32 Address on chain B (any valid address)
275///     }
276/// ]
277/// ```
278#[cw_serde]
279pub struct SendNativeInfo {
280    pub coin: Coin,
281    pub path_middle_forward: Vec<PacketPath>,
282    pub dest_denom: String,
283    pub channel_id: String,
284    pub timeout: Option<u64>,
285}
286
287impl SendNativeInfo {
288    pub fn get_first_channel(self) -> String {
289        match self.path_middle_forward.first() {
290            Some(first) => first.channel_id.to_owned(),
291            None => self.channel_id,
292        }
293    }
294}
295
296#[cw_serde]
297pub struct PacketPath {
298    /// Channel opened to `transfer` port
299    pub channel_id: String,
300    /// Chain `denom` saved in `gate` contract
301    pub address: String,
302}
303
304#[cw_serde]
305pub struct GateQueryResponse {
306    pub request: QueryRequest<Empty>,
307    pub response: Binary,
308}
309
310#[cw_serde]
311pub struct QueryRequestInfoResponse {
312    pub queries: Vec<GateQueryResponse>,
313    pub from_contract: String,
314    pub callback_msg: Option<Binary>,
315}
316
317#[cw_serde]
318pub struct ChannelInfo {
319    /// id of this channel
320    pub src_channel_id: String,
321    /// the remote port we connect to
322    pub dest_port_id: String,
323    /// the remote channel we connect to
324    pub dest_channel_id: String,
325    /// the connection this exists on (you can use to query client/consensus info)
326    pub connection_id: String,
327    /// base denom of remote chain
328    pub base_denom: Option<String>,
329    /// base denom of remote chain
330    pub voucher_contract: Option<String>,
331}
332
333impl ChannelInfo {
334    pub fn get_remote_gate(self) -> StdResult<String> {
335        let slice: Vec<&str> = self.dest_port_id.split('.').collect();
336
337        if slice.len() != 2 {
338            return Err(StdError::generic_err("dest_port_id is invalid"));
339        }
340
341        return Ok(slice.last().unwrap().to_string());
342    }
343}
344
345/// --- FUNCTIONS ---
346
347#[cfg(feature = "gate")]
348pub fn merge_send_native(
349    from: &Option<SendNativeInfo>,
350    with: &Option<SendNativeInfo>,
351) -> StdResult<Option<SendNativeInfo>> {
352    match from {
353        Some(from) => match with.clone() {
354            Some(with) => {
355                if from.coin.denom != with.coin.denom {
356                    return Err(StdError::generic_err("Multiple native coin denom detected"));
357                }
358
359                if from.path_middle_forward != with.path_middle_forward {
360                    return Err(StdError::generic_err(
361                        "Multiple path_middle_forward detected",
362                    ));
363                }
364
365                if from.dest_denom != with.dest_denom {
366                    return Err(StdError::generic_err("Multiple dest_denom detected"));
367                }
368
369                if from.channel_id != with.channel_id {
370                    return Err(StdError::generic_err("Multiple channel_id detected"));
371                }
372
373                Ok(Some(SendNativeInfo {
374                    coin: Coin {
375                        denom: from.coin.denom.clone(),
376                        amount: from.coin.amount + with.coin.amount,
377                    },
378                    path_middle_forward: from.path_middle_forward.clone(),
379                    dest_denom: from.dest_denom.clone(),
380                    channel_id: from.channel_id.clone(),
381                    timeout: lowest_timeout(from.timeout, with.timeout)?,
382                }))
383            }
384            None => Ok(Some(from.to_owned())),
385        },
386        None => Ok(with.to_owned()),
387    }
388}
389
390#[cfg(feature = "gate")]
391pub fn lowest_timeout(from: Option<u64>, with: Option<u64>) -> StdResult<Option<u64>> {
392    match from {
393        Some(from) => match with {
394            Some(with) => {
395                if with > from {
396                    Ok(Some(from))
397                } else {
398                    Ok(Some(with))
399                }
400            }
401            None => Ok(Some(from)),
402        },
403        None => Ok(with),
404    }
405}