abstract_client/
builder.rs

1//! # Abstract client Test utilities
2//!
3//! This module provides useful helpers for integration tests
4
5use abstract_interface::{Abstract, ExecuteMsgFns};
6use abstract_std::objects::{
7    pool_id::UncheckedPoolAddress, PoolMetadata, UncheckedChannelEntry, UncheckedContractEntry,
8};
9use cw_asset::AssetInfoUnchecked;
10use cw_orch::prelude::*;
11
12use crate::client::{AbstractClient, AbstractClientResult};
13
14impl<Chain: CwEnv> AbstractClient<Chain> {
15    /// Abstract client builder
16    pub fn builder(chain: Chain) -> AbstractClientBuilder<Chain> {
17        AbstractClientBuilder::new(chain)
18    }
19
20    #[cfg(feature = "test-utils")]
21    /// Cw20 contract builder
22    pub fn cw20_builder(
23        &self,
24        name: impl Into<String>,
25        symbol: impl Into<String>,
26        decimals: u8,
27    ) -> self::cw20_builder::Cw20Builder<Chain> {
28        use crate::Environment;
29        self::cw20_builder::Cw20Builder::new(
30            self.environment(),
31            name.into(),
32            symbol.into(),
33            decimals,
34        )
35    }
36}
37
38/// A builder for setting up tests for `Abstract` in an environment where Abstract isn't deployed yet.
39/// Example: [`Mock`](cw_orch::prelude::Mock) or a local [`Daemon`](cw_orch::prelude::Daemon).
40pub struct AbstractClientBuilder<Chain: CwEnv> {
41    chain: Chain,
42    dexes: Vec<String>,
43    contracts: Vec<(UncheckedContractEntry, String)>,
44    assets: Vec<(String, AssetInfoUnchecked)>,
45    channels: Vec<(UncheckedChannelEntry, String)>,
46    pools: Vec<(UncheckedPoolAddress, PoolMetadata)>,
47}
48
49impl<Chain: CwEnv> AbstractClientBuilder<Chain> {
50    pub(crate) fn new(chain: Chain) -> Self {
51        Self {
52            chain,
53            dexes: vec![],
54            contracts: vec![],
55            assets: vec![],
56            channels: vec![],
57            pools: vec![],
58        }
59    }
60
61    /// Register contract on Abstract Name Service
62    pub fn contract(
63        &mut self,
64        contract_entry: UncheckedContractEntry,
65        address: impl Into<String>,
66    ) -> &mut Self {
67        self.contracts.push((contract_entry, address.into()));
68        self
69    }
70
71    /// Register contracts on Abstract Name Service
72    pub fn contracts(&mut self, contracts: Vec<(UncheckedContractEntry, String)>) -> &mut Self {
73        self.contracts = contracts;
74        self
75    }
76
77    /// Register asset on Abstract Name Service
78    pub fn asset(&mut self, name: impl Into<String>, asset_info: AssetInfoUnchecked) -> &mut Self {
79        self.assets.push((name.into(), asset_info));
80        self
81    }
82
83    /// Register assets on Abstract Name Service
84    pub fn assets(&mut self, assets: Vec<(String, AssetInfoUnchecked)>) -> &mut Self {
85        self.assets = assets;
86        self
87    }
88
89    /// Register ibc channel on Abstract Name Service
90    pub fn channel(
91        &mut self,
92        channel_entry: UncheckedChannelEntry,
93        channel_value: impl Into<String>,
94    ) -> &mut Self {
95        self.channels.push((channel_entry, channel_value.into()));
96        self
97    }
98
99    /// Register ibc channels on Abstract Name Service
100    pub fn channels(&mut self, channels: Vec<(UncheckedChannelEntry, String)>) -> &mut Self {
101        self.channels = channels;
102        self
103    }
104
105    /// Register liquidity pool on Abstract Name Service
106    pub fn pool(
107        &mut self,
108        pool_address: UncheckedPoolAddress,
109        pool_metadata: PoolMetadata,
110    ) -> &mut Self {
111        self.pools.push((pool_address, pool_metadata));
112        self
113    }
114
115    /// Register liquidity pools on Abstract Name Service
116    pub fn pools(&mut self, pools: Vec<(UncheckedPoolAddress, PoolMetadata)>) -> &mut Self {
117        self.pools = pools;
118        self
119    }
120
121    /// Register dex on Abstract Name Service
122    pub fn dex(&mut self, dex: &str) -> &mut Self {
123        self.dexes.push(dex.to_string());
124        self
125    }
126
127    /// Register dexes on Abstract Name Service
128    pub fn dexes(&mut self, dexes: Vec<String>) -> &mut Self {
129        self.dexes = dexes;
130        self
131    }
132
133    /// Deploy abstract with current configuration
134    pub fn build(&self) -> AbstractClientResult<AbstractClient<Chain>> {
135        let abstr = Abstract::deploy_on(self.chain.clone(), ())?;
136        self.update_ans(&abstr)?;
137
138        AbstractClient::new(self.chain.clone())
139    }
140
141    fn update_ans(&self, abstr: &Abstract<Chain>) -> AbstractClientResult<()> {
142        let ans_host = &abstr.ans_host;
143        if !self.dexes.is_empty() {
144            ans_host.update_dexes(self.dexes.clone(), vec![])?;
145        }
146        if !self.contracts.is_empty() {
147            ans_host.update_contract_addresses(self.contracts.clone(), vec![])?;
148        }
149        if !self.assets.is_empty() {
150            ans_host.update_asset_addresses(self.assets.clone(), vec![])?;
151        }
152        if !self.channels.is_empty() {
153            ans_host.update_channels(self.channels.clone(), vec![])?;
154        }
155        if !self.pools.is_empty() {
156            ans_host.update_pools(self.pools.clone(), vec![])?;
157        }
158
159        Ok(())
160    }
161}
162
163#[cfg(feature = "test-utils")]
164pub mod cw20_builder {
165    //! # CW20 Builder
166
167    // Re-exports to limit dependencies for consumer.
168    use cosmwasm_std::Addr;
169    pub use cw20::*;
170    pub use cw20_base::msg::InstantiateMarketingInfo;
171    use cw_orch::{environment::CwEnv, prelude::*};
172    pub use cw_plus_interface::cw20_base::{
173        Cw20Base, ExecuteMsgInterfaceFns, InstantiateMsg, QueryMsgInterfaceFns,
174    };
175
176    use crate::client::AbstractClientResult;
177
178    /// A builder for creating and deploying `Cw20` contract in a [`CwEnv`](cw_orch::prelude::CwEnv) environment.
179    ///
180    /// Use the builder methods to specifiy the details of your cw20 token.
181    ///
182    /// Use [`Self::instantiate_with_id`] to upload and instantiate your token.
183    pub struct Cw20Builder<Chain: CwEnv> {
184        chain: Chain,
185        name: String,
186        symbol: String,
187        decimals: u8,
188        initial_balances: Vec<Cw20Coin>,
189        mint: Option<MinterResponse>,
190        marketing: Option<InstantiateMarketingInfo>,
191        admin: Option<Addr>,
192    }
193
194    impl<Chain: CwEnv> Cw20Builder<Chain> {
195        /// Creates a new [`Cw20Builder`]. Call [`crate::AbstractClient`] to create.
196        pub(crate) fn new(chain: Chain, name: String, symbol: String, decimals: u8) -> Self {
197            Self {
198                chain,
199                name,
200                symbol,
201                decimals,
202                initial_balances: vec![],
203                mint: None,
204                marketing: None,
205                admin: None,
206            }
207        }
208
209        /// Set initial cw20 balance
210        pub fn initial_balance(&mut self, initial_balance: Cw20Coin) -> &mut Self {
211            self.initial_balances.push(initial_balance);
212            self
213        }
214
215        /// Set initial cw20 balances
216        pub fn initial_balances(&mut self, initial_balances: Vec<Cw20Coin>) -> &mut Self {
217            self.initial_balances = initial_balances;
218            self
219        }
220
221        /// Set minter
222        pub fn mint(&mut self, mint: MinterResponse) -> &mut Self {
223            self.mint = Some(mint);
224            self
225        }
226
227        /// Set marketing info
228        pub fn marketing(&mut self, marketing: InstantiateMarketingInfo) -> &mut Self {
229            self.marketing = Some(marketing);
230            self
231        }
232
233        /// Set admin
234        pub fn admin(&mut self, admin: impl Into<String>) -> &mut Self {
235            self.admin = Some(Addr::unchecked(admin.into()));
236            self
237        }
238
239        /// Instantiate with provided module id
240        pub fn instantiate_with_id(&self, id: &str) -> AbstractClientResult<Cw20Base<Chain>> {
241            let cw20 = Cw20Base::new(id, self.chain.clone());
242            cw20.upload()?;
243            cw20.instantiate(
244                &InstantiateMsg {
245                    decimals: self.decimals,
246                    mint: self.mint.clone(),
247                    symbol: self.symbol.clone(),
248                    name: self.name.clone(),
249                    initial_balances: self.initial_balances.clone(),
250                    marketing: self.marketing.clone(),
251                },
252                self.admin.as_ref(),
253                &[],
254            )?;
255            Ok(cw20)
256        }
257    }
258}