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
134
135
136
use crate::{
    error::AppResult,
    ibc::IbcChannelCreator,
    ibc_module::IbcPacketType,
    iper_app::{IperAppRef, MayResponse},
};
use anyhow::anyhow;
use std::{cell::RefCell, collections::BTreeMap, rc::Rc};

#[derive(Default)]
/// This structure acts as a wrapper containing all [`IperApp`](crate::iper_app::IperApp).
///
/// Its primary purpose is to relay `IBC` `packets` and to facilitate the creation of `IBC` `channels` among various [`IperApp`](crate::iper_app::IperApp).
///
/// [`IperApp`](crate::iper_app::IperApp)s are stored as [`IperAppRef`] traits instead of as [`IperApp`](crate::iper_app::IperApp) instances.
/// This approach is used to decouple them from the specific typing of the generic parameters
/// required by the [`IperApp`](crate::iper_app::IperApp) and [`App`](cw_multi_test::App) classes.
pub struct Ecosystem {
    apps: BTreeMap<String, Rc<RefCell<dyn IperAppRef>>>,
}

impl Ecosystem {
    /// Add a [`IperApp`](crate::iper_app::IperApp) as [`IperAppRef`]
    pub fn add_app(self, app: Rc<RefCell<dyn IperAppRef>>) -> Self {
        let mut apps = self.apps;
        let chain_id = app.borrow().chain_id().to_string();
        apps.insert(chain_id, app);
        Self { apps }
    }

    /// Open a `IbcChannel` bewteen two [`IperApp`](crate::iper_app::IperApp)
    pub fn open_ibc_channel(
        &self,
        mut channel_1: IbcChannelCreator,
        mut channel_2: IbcChannelCreator,
    ) -> AppResult<()> {
        let app_1 = self.get_app(&channel_1.chain_id)?;
        let app_2 = self.get_app(&channel_2.chain_id)?;

        let channel_id_1 = app_1.borrow().get_next_channel_id();
        let channel_id_2 = app_2.borrow().get_next_channel_id();
        channel_1.set_channel_id(channel_id_1);
        channel_2.set_channel_id(channel_id_2);

        let sequence = Rc::new(RefCell::new(0));

        app_1
            .borrow_mut()
            .open_channel(&channel_1, &channel_2, sequence.clone())?;
        app_2
            .borrow_mut()
            .open_channel(&channel_2, &channel_1, sequence)?;

        app_1.borrow_mut().channel_connect(channel_id_1)?;
        app_2.borrow_mut().channel_connect(channel_id_2)?;

        app_1.borrow_mut().channel_connect(channel_id_1)?;
        app_2.borrow_mut().channel_connect(channel_id_2)?;

        Ok(())
    }

    /// Relay all `packets` untill not `packets` are in pending.
    /// The order is based on the [`BTreeMap`] key orders.
    /// Iterating all [`IperApp`](crate::iper_app::IperApp), if one [`IperApp`](crate::iper_app::IperApp) has not pending packets, next [`IperApp`](crate::iper_app::IperApp) is checked.
    /// Once one `packet` is `relayed`, the loop is restarted from the first [`IperApp`](crate::iper_app::IperApp)
    pub fn relay_all_packets(&self) -> AppResult<Vec<MayResponse>> {
        let mut res = vec![];

        let mut finished = false;

        while !finished {
            finished = true;

            for (chain_id, app) in &self.apps {
                if app.borrow().some_pending_packets() {
                    res.push(self.relay_next_packet(chain_id)?);
                    finished = false;
                    break;
                }
            }
        }

        Ok(res)
    }

    /// Relay the next `packet` of a specific [`IperApp`](crate::iper_app::IperApp)
    pub fn relay_next_packet(&self, chain_id: impl Into<String> + Clone) -> AppResult<MayResponse> {
        let app = self.get_app(chain_id.clone())?;
        let packet_id = app.borrow().get_next_pending_packet()?;
        self.relay_packet(chain_id, packet_id)
    }

    /// Relay as specific `packet` of a specific [`IperApp`](crate::iper_app::IperApp)
    pub fn relay_packet(
        &self,
        chain_id: impl Into<String>,
        packet_id: u64,
    ) -> AppResult<MayResponse> {
        let app_src = self.get_app(chain_id)?;

        let packet = app_src.borrow().get_pending_packet(packet_id)?;

        let channel_info = app_src
            .borrow()
            .get_channel_info(packet.get_local_channel_id())?;

        let app_dest = self.get_app(&channel_info.remote.chain_id)?;

        let response = app_dest.borrow_mut().incoming_packet(packet)?;

        app_src.borrow_mut().remove_packet(packet_id)?;

        Ok(response)
    }

    /// Return all pending `packets` between all [`IperApp`](crate::iper_app::IperApp)
    pub fn get_all_pending_packets(
        &self,
    ) -> AppResult<BTreeMap<String, BTreeMap<u64, IbcPacketType>>> {
        let mut map = BTreeMap::new();

        for (chain_id, app) in &self.apps {
            map.insert(chain_id.clone(), app.borrow().get_pending_packets()?);
        }

        Ok(map)
    }

    fn get_app(&self, chain_id: impl Into<String>) -> AppResult<&Rc<RefCell<dyn IperAppRef>>> {
        let chain_id: String = chain_id.into();
        self.apps
            .get(&chain_id)
            .ok_or(anyhow!("App not found for chain_id: {}", chain_id))
    }
}