#![cfg(feature = "stargate")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::cmp::{Ord, Ordering, PartialOrd};
use std::fmt;
use crate::binary::Binary;
use crate::coins::Coin;
use crate::results::{Attribute, CosmosMsg, Empty, SubMsg};
use crate::timestamp::Timestamp;
#[non_exhaustive]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum IbcMsg {
Transfer {
channel_id: String,
to_address: String,
amount: Coin,
timeout: IbcTimeout,
},
SendPacket {
channel_id: String,
data: Binary,
timeout: IbcTimeout,
},
CloseChannel { channel_id: String },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct IbcEndpoint {
pub port_id: String,
pub channel_id: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct IbcTimeout {
block: Option<IbcTimeoutBlock>,
timestamp: Option<Timestamp>,
}
impl IbcTimeout {
pub fn with_block(block: IbcTimeoutBlock) -> Self {
IbcTimeout {
block: Some(block),
timestamp: None,
}
}
pub fn with_timestamp(timestamp: Timestamp) -> Self {
IbcTimeout {
block: None,
timestamp: Some(timestamp),
}
}
pub fn with_both(block: IbcTimeoutBlock, timestamp: Timestamp) -> Self {
IbcTimeout {
block: Some(block),
timestamp: Some(timestamp),
}
}
pub fn block(&self) -> Option<IbcTimeoutBlock> {
self.block
}
pub fn timestamp(&self) -> Option<Timestamp> {
self.timestamp
}
}
impl From<Timestamp> for IbcTimeout {
fn from(timestamp: Timestamp) -> IbcTimeout {
IbcTimeout::with_timestamp(timestamp)
}
}
impl From<IbcTimeoutBlock> for IbcTimeout {
fn from(original: IbcTimeoutBlock) -> IbcTimeout {
IbcTimeout::with_block(original)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct IbcChannel {
pub endpoint: IbcEndpoint,
pub counterparty_endpoint: IbcEndpoint,
pub order: IbcOrder,
pub version: String,
pub counterparty_version: Option<String>,
pub connection_id: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub enum IbcOrder {
#[serde(rename = "ORDER_UNORDERED")]
Unordered,
#[serde(rename = "ORDER_ORDERED")]
Ordered,
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct IbcTimeoutBlock {
pub revision: u64,
pub height: u64,
}
impl IbcTimeoutBlock {
pub fn is_zero(&self) -> bool {
self.revision == 0 && self.height == 0
}
}
impl PartialOrd for IbcTimeoutBlock {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for IbcTimeoutBlock {
fn cmp(&self, other: &Self) -> Ordering {
match self.revision.cmp(&other.revision) {
Ordering::Equal => self.height.cmp(&other.height),
other => other,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct IbcPacket {
pub data: Binary,
pub src: IbcEndpoint,
pub dest: IbcEndpoint,
pub sequence: u64,
pub timeout: IbcTimeout,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct IbcAcknowledgement {
pub acknowledgement: Binary,
pub original_packet: IbcPacket,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct IbcBasicResponse<T = Empty>
where
T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
pub submessages: Vec<SubMsg<T>>,
pub messages: Vec<CosmosMsg<T>>,
pub attributes: Vec<Attribute>,
}
impl<T> Default for IbcBasicResponse<T>
where
T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
fn default() -> Self {
IbcBasicResponse {
submessages: vec![],
messages: vec![],
attributes: vec![],
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct IbcReceiveResponse<T = Empty>
where
T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
pub acknowledgement: Binary,
pub submessages: Vec<SubMsg<T>>,
pub messages: Vec<CosmosMsg<T>>,
pub attributes: Vec<Attribute>,
}
impl<T> Default for IbcReceiveResponse<T>
where
T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
fn default() -> Self {
IbcReceiveResponse {
acknowledgement: Binary(vec![]),
submessages: vec![],
messages: vec![],
attributes: vec![],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json_wasm::to_string;
#[test]
fn serialize_msg() {
let msg = IbcMsg::Transfer {
channel_id: "channel-123".to_string(),
to_address: "my-special-addr".into(),
amount: Coin::new(12345678, "uatom"),
timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(1234567890)),
};
let encoded = to_string(&msg).unwrap();
let expected = r#"{"transfer":{"channel_id":"channel-123","to_address":"my-special-addr","amount":{"denom":"uatom","amount":"12345678"},"timeout":{"block":null,"timestamp":"1234567890"}}}"#;
assert_eq!(encoded.as_str(), expected);
}
#[test]
fn ibc_timeout_serialize() {
let timestamp = IbcTimeout::with_timestamp(Timestamp::from_nanos(684816844));
let expected = r#"{"block":null,"timestamp":"684816844"}"#;
assert_eq!(to_string(×tamp).unwrap(), expected);
let block = IbcTimeout::with_block(IbcTimeoutBlock {
revision: 12,
height: 129,
});
let expected = r#"{"block":{"revision":12,"height":129},"timestamp":null}"#;
assert_eq!(to_string(&block).unwrap(), expected);
let both = IbcTimeout::with_both(
IbcTimeoutBlock {
revision: 12,
height: 129,
},
Timestamp::from_nanos(684816844),
);
let expected = r#"{"block":{"revision":12,"height":129},"timestamp":"684816844"}"#;
assert_eq!(to_string(&both).unwrap(), expected);
}
#[test]
fn ibc_timeout_block_ord() {
let epoch1a = IbcTimeoutBlock {
revision: 1,
height: 1000,
};
let epoch1b = IbcTimeoutBlock {
revision: 1,
height: 3000,
};
let epoch2a = IbcTimeoutBlock {
revision: 2,
height: 500,
};
let epoch2b = IbcTimeoutBlock {
revision: 2,
height: 2500,
};
assert!(epoch1a == epoch1a);
assert!(epoch1a < epoch1b);
assert!(epoch1b > epoch1a);
assert!(epoch2a > epoch1a);
assert!(epoch2b > epoch1a);
assert!(epoch1b > epoch1a);
assert!(epoch2a > epoch1b);
assert!(epoch2b > epoch2a);
assert!(epoch2b > epoch1b);
assert!(epoch1a < epoch1b);
assert!(epoch1b < epoch2a);
assert!(epoch2a < epoch2b);
assert!(epoch1b < epoch2b);
}
#[test]
fn ibc_packet_serialize() {
let packet = IbcPacket {
data: b"foo".into(),
src: IbcEndpoint {
port_id: "their-port".to_string(),
channel_id: "channel-1234".to_string(),
},
dest: IbcEndpoint {
port_id: "our-port".to_string(),
channel_id: "chan33".into(),
},
sequence: 27,
timeout: IbcTimeout::with_both(
IbcTimeoutBlock {
revision: 1,
height: 12345678,
},
Timestamp::from_nanos(4611686018427387904),
),
};
let expected = r#"{"data":"Zm9v","src":{"port_id":"their-port","channel_id":"channel-1234"},"dest":{"port_id":"our-port","channel_id":"chan33"},"sequence":27,"timeout":{"block":{"revision":1,"height":12345678},"timestamp":"4611686018427387904"}}"#;
assert_eq!(to_string(&packet).unwrap(), expected);
let no_timestamp = IbcPacket {
data: b"foo".into(),
src: IbcEndpoint {
port_id: "their-port".to_string(),
channel_id: "channel-1234".to_string(),
},
dest: IbcEndpoint {
port_id: "our-port".to_string(),
channel_id: "chan33".into(),
},
sequence: 27,
timeout: IbcTimeout::with_block(IbcTimeoutBlock {
revision: 1,
height: 12345678,
}),
};
let expected = r#"{"data":"Zm9v","src":{"port_id":"their-port","channel_id":"channel-1234"},"dest":{"port_id":"our-port","channel_id":"chan33"},"sequence":27,"timeout":{"block":{"revision":1,"height":12345678},"timestamp":null}}"#;
assert_eq!(to_string(&no_timestamp).unwrap(), expected);
}
}