abstract_sdk/apis/ibc_memo/
pfm.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::collections::BTreeMap;

use serde_cw_value::Value;

/// Builder for [Packet Forward Middleware](https://github.com/cosmos/ibc-apps/tree/main/middleware/packet-forward-middleware) memos.
pub struct PfmMemoBuilder {
    port: Option<String>,
    hops: Vec<PacketForwardMiddlewareHop>,
}

impl PfmMemoBuilder {
    /// Forward memo builder
    pub fn new(first_hop_channel: impl Into<String>) -> Self {
        Self {
            port: None,
            hops: vec![PacketForwardMiddlewareHop::new(first_hop_channel)],
        }
    }

    /// Port, defaults to "transfer"
    pub fn port(mut self, port: impl Into<String>) -> Self {
        self.port = Some(port.into());
        self
    }

    /// Channel hop
    pub fn hop(mut self, channel: impl Into<String>) -> Self {
        self.hops.push(PacketForwardMiddlewareHop::new(channel));
        self
    }

    /// Hop modifier (applies only on last added hop):
    /// Timeout duration, for example: "10m"
    pub fn timeout(mut self, timeout: impl Into<String>) -> Self {
        if let Some(last_hop) = self.hops.last_mut() {
            last_hop.timeout = Some(timeout.into());
        }
        self
    }

    /// Hop modifier (applies only on last added hop):
    /// Retries number
    pub fn retries(mut self, retries: u8) -> Self {
        if let Some(last_hop) = self.hops.last_mut() {
            last_hop.retries = Some(retries);
        }
        self
    }

    /// Build the memo json string
    /// Receiver is an address of the packet receiver on remote chain
    pub fn build(self, receiver: impl Into<String>) -> cosmwasm_std::StdResult<String> {
        let PfmMemoBuilder { port, hops } = self;
        let receiver = receiver.into();
        let port = port.unwrap_or("transfer".to_owned());

        let mut forwards = hops
            .into_iter()
            .map(|hop| ForwardMemo {
                receiver: None,
                port: port.clone(),
                channel: hop.channel,
                timeout: hop.timeout,
                retries: hop.retries,
            })
            .collect::<Vec<_>>();
        // Destination have to know receiver
        if let Some(last_hop) = forwards.last_mut() {
            last_hop.receiver = Some(receiver);
        }

        // Building message from behind because it's easier to satisfy borrow checker this way
        let mut head = BTreeMap::new();
        for forward in forwards.into_iter().rev() {
            let mut forward_msg = forward.build_value_map();
            if !head.is_empty() {
                let next = head;
                forward_msg.insert(Value::String("next".to_owned()), Value::Map(next));
            }
            head = BTreeMap::from([(Value::String("forward".to_owned()), Value::Map(forward_msg))]);
        }
        cosmwasm_std::to_json_string(&head)
    }
}

struct PacketForwardMiddlewareHop {
    channel: String,
    timeout: Option<String>,
    retries: Option<u8>,
}

impl PacketForwardMiddlewareHop {
    pub fn new(channel: impl Into<String>) -> Self {
        Self {
            channel: channel.into(),
            timeout: None,
            retries: None,
        }
    }
}

// Forward structure
struct ForwardMemo {
    receiver: Option<String>,
    port: String,
    channel: String,
    timeout: Option<String>,
    retries: Option<u8>,
}

impl ForwardMemo {
    fn build_value_map(self) -> BTreeMap<Value, Value> {
        let receiver = self.receiver.unwrap_or("pfm".to_owned());
        let mut forward_value = BTreeMap::from([
            (
                Value::String("receiver".to_owned()),
                Value::String(receiver),
            ),
            (Value::String("port".to_owned()), Value::String(self.port)),
            (
                Value::String("channel".to_owned()),
                Value::String(self.channel),
            ),
        ]);
        if let Some(timeout) = self.timeout {
            forward_value.insert(Value::String("timeout".to_owned()), Value::String(timeout));
        }
        if let Some(retries) = self.retries {
            forward_value.insert(Value::String("retries".to_owned()), Value::U8(retries));
        }
        forward_value
    }
}