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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//! # Abstract client Test utilities
//!
//! This module provides useful helpers for integration tests

use abstract_core::{
    self,
    objects::{
        pool_id::UncheckedPoolAddress, PoolMetadata, UncheckedChannelEntry, UncheckedContractEntry,
    },
};
use abstract_interface::{Abstract, ExecuteMsgFns};
use cw_asset::AssetInfoUnchecked;
use cw_orch::{deploy::Deploy, environment::CwEnv};

use crate::{
    client::{AbstractClient, AbstractClientResult},
    Environment,
};

use self::cw20_builder::Cw20Builder;

impl<Chain: CwEnv> AbstractClient<Chain> {
    /// Abstract client builder
    pub fn builder(chain: Chain) -> AbstractClientBuilder<Chain> {
        AbstractClientBuilder::new(chain)
    }

    /// Cw20 contract builder
    pub fn cw20_builder(
        &self,
        name: impl Into<String>,
        symbol: impl Into<String>,
        decimals: u8,
    ) -> Cw20Builder<Chain> {
        Cw20Builder::new(self.environment(), name.into(), symbol.into(), decimals)
    }
}

/// A builder for setting up tests for `Abstract` in a [`Mock`] environment.
pub struct AbstractClientBuilder<Chain: CwEnv> {
    chain: Chain,
    contracts: Vec<(UncheckedContractEntry, String)>,
    assets: Vec<(String, AssetInfoUnchecked)>,
    channels: Vec<(UncheckedChannelEntry, String)>,
    pools: Vec<(UncheckedPoolAddress, PoolMetadata)>,
}

impl<Chain: CwEnv> AbstractClientBuilder<Chain> {
    pub(crate) fn new(chain: Chain) -> Self {
        Self {
            chain,
            contracts: vec![],
            assets: vec![],
            channels: vec![],
            pools: vec![],
        }
    }

    /// Register contract on Abstract Name Service
    pub fn contract(
        &mut self,
        contract_entry: UncheckedContractEntry,
        address: impl Into<String>,
    ) -> &mut Self {
        self.contracts.push((contract_entry, address.into()));
        self
    }

    /// Register contracts on Abstract Name Service
    pub fn contracts(&mut self, contracts: Vec<(UncheckedContractEntry, String)>) -> &mut Self {
        self.contracts = contracts;
        self
    }

    /// Register asset on Abstract Name Service
    pub fn asset(&mut self, name: impl Into<String>, asset_info: AssetInfoUnchecked) -> &mut Self {
        self.assets.push((name.into(), asset_info));
        self
    }

    /// Register assets on Abstract Name Service
    pub fn assets(&mut self, assets: Vec<(String, AssetInfoUnchecked)>) -> &mut Self {
        self.assets = assets;
        self
    }

    /// Register ibc channel on Abstract Name Service
    pub fn channel(
        &mut self,
        channel_entry: UncheckedChannelEntry,
        channel_value: impl Into<String>,
    ) -> &mut Self {
        self.channels.push((channel_entry, channel_value.into()));
        self
    }

    /// Register ibc channels on Abstract Name Service
    pub fn channels(&mut self, channels: Vec<(UncheckedChannelEntry, String)>) -> &mut Self {
        self.channels = channels;
        self
    }

    /// Register liquidity pool on Abstract Name Service
    pub fn pool(
        &mut self,
        pool_address: UncheckedPoolAddress,
        pool_metadata: PoolMetadata,
    ) -> &mut Self {
        self.pools.push((pool_address, pool_metadata));
        self
    }

    /// Register liquidity pools on Abstract Name Service
    pub fn pools(&mut self, pools: Vec<(UncheckedPoolAddress, PoolMetadata)>) -> &mut Self {
        self.pools = pools;
        self
    }

    /// Deploy abstract with current configuration
    pub fn build(&self) -> AbstractClientResult<AbstractClient<Chain>> {
        let abstr = Abstract::deploy_on(self.chain.clone(), self.chain.sender().into_string())?;
        self.update_ans(&abstr)?;

        AbstractClient::new(self.chain.clone())
    }

    fn update_ans(&self, abstr: &Abstract<Chain>) -> AbstractClientResult<()> {
        abstr
            .ans_host
            .update_contract_addresses(self.contracts.clone(), vec![])?;
        abstr
            .ans_host
            .update_asset_addresses(self.assets.clone(), vec![])?;
        abstr
            .ans_host
            .update_channels(self.channels.clone(), vec![])?;
        abstr.ans_host.update_pools(self.pools.clone(), vec![])?;

        Ok(())
    }
}

pub mod cw20_builder {
    //! # CW20 Builder

    // Re-exports to limit dependencies for consumer.
    pub use cw20::{msg::Cw20ExecuteMsgFns, *};
    pub use cw20_base::msg::{InstantiateMarketingInfo, QueryMsgFns as Cw20QueryMsgFns};
    pub use cw_plus_interface::cw20_base::Cw20Base;

    use cosmwasm_std::Addr;

    use cw_orch::{
        environment::CwEnv,
        prelude::{CwOrchInstantiate, CwOrchUpload},
    };
    use cw_plus_interface::cw20_base::InstantiateMsg;

    use crate::client::AbstractClientResult;

    /// A builder for creating and deploying `Cw20` contract in a [`Mock`] environment.
    pub struct Cw20Builder<Chain: CwEnv> {
        chain: Chain,
        name: String,
        symbol: String,
        decimals: u8,
        initial_balances: Vec<Cw20Coin>,
        mint: Option<MinterResponse>,
        marketing: Option<InstantiateMarketingInfo>,
        admin: Option<Addr>,
    }

    impl<Chain: CwEnv> Cw20Builder<Chain> {
        /// Creates a new [`Cw20Builder`]. Call [`crate::AbstractClient`] to create.
        pub(crate) fn new(chain: Chain, name: String, symbol: String, decimals: u8) -> Self {
            Self {
                chain,
                name,
                symbol,
                decimals,
                initial_balances: vec![],
                mint: None,
                marketing: None,
                admin: None,
            }
        }

        /// Set initial cw20 balance
        pub fn initial_balance(&mut self, initial_balance: Cw20Coin) -> &mut Self {
            self.initial_balances.push(initial_balance);
            self
        }

        /// Set initial cw20 balances
        pub fn initial_balances(&mut self, initial_balances: Vec<Cw20Coin>) -> &mut Self {
            self.initial_balances = initial_balances;
            self
        }

        /// Set minter
        pub fn mint(&mut self, mint: MinterResponse) -> &mut Self {
            self.mint = Some(mint);
            self
        }

        /// Set marketing info
        pub fn marketing(&mut self, marketing: InstantiateMarketingInfo) -> &mut Self {
            self.marketing = Some(marketing);
            self
        }

        /// Set admin
        pub fn admin(&mut self, admin: impl Into<String>) -> &mut Self {
            self.admin = Some(Addr::unchecked(admin.into()));
            self
        }

        /// Instantiate with provided module id
        // TODO: we can rename it to `build()` as other methods and take {module-id}-{symbol} as id instead
        pub fn instantiate_with_id(&self, id: &str) -> AbstractClientResult<Cw20Base<Chain>> {
            let cw20 = Cw20Base::new(id, self.chain.clone());

            // TODO: Consider adding error if the code-id is already uploaded. This would
            // imply that the user is trying to instantiate twice using the same id which would
            // overwrite the state.
            cw20.upload()?;
            cw20.instantiate(
                &InstantiateMsg {
                    decimals: self.decimals,
                    mint: self.mint.clone(),
                    symbol: self.symbol.clone(),
                    name: self.name.clone(),
                    initial_balances: self.initial_balances.clone(),
                    marketing: self.marketing.clone(),
                },
                self.admin.as_ref(),
                None,
            )?;
            Ok(cw20)
        }
    }
}