fuels_test_helpers/
lib.rs

1//! Testing helpers/utilities for Fuel SDK.
2extern crate core;
3
4#[cfg(feature = "fuels-accounts")]
5pub use accounts::*;
6use fuel_tx::{Bytes32, ConsensusParameters, ContractParameters, TxParameters, UtxoId};
7use fuel_types::{AssetId, Nonce};
8use fuels_accounts::provider::Provider;
9use fuels_core::types::{
10    Address,
11    coin::Coin,
12    errors::Result,
13    message::{Message, MessageStatus},
14};
15pub use node_types::*;
16use rand::{Fill, Rng, SeedableRng, rngs::StdRng};
17use utils::{into_coin_configs, into_message_configs};
18pub use wallets_config::*;
19mod node_types;
20
21#[cfg(not(feature = "fuel-core-lib"))]
22pub(crate) mod fuel_bin_service;
23
24#[cfg(feature = "fuels-accounts")]
25mod accounts;
26
27pub use service::*;
28mod service;
29
30mod utils;
31mod wallets_config;
32
33/// Create a vector of `num_asset`*`coins_per_asset` UTXOs and a vector of the unique corresponding
34/// asset IDs. `AssetId`. Each UTXO (=coin) contains `amount_per_coin` amount of a random asset. The
35/// output of this function can be used with `setup_test_provider` to get a client with some
36/// pre-existing coins, with `num_asset` different asset ids. Note that one of the assets is the
37/// base asset to pay for gas.
38pub fn setup_multiple_assets_coins(
39    owner: Address,
40    num_asset: u64,
41    coins_per_asset: u64,
42    amount_per_coin: u64,
43) -> (Vec<Coin>, Vec<AssetId>) {
44    let mut rng = rand::thread_rng();
45    // Create `num_asset-1` asset ids so there is `num_asset` in total with the base asset
46    let asset_ids = (0..(num_asset - 1))
47        .map(|_| {
48            let mut random_asset_id = AssetId::zeroed();
49            random_asset_id
50                .try_fill(&mut rng)
51                .expect("failed to fill with random data");
52            random_asset_id
53        })
54        .chain([AssetId::zeroed()])
55        .collect::<Vec<AssetId>>();
56
57    let coins = asset_ids
58        .iter()
59        .flat_map(|id| setup_single_asset_coins(owner, *id, coins_per_asset, amount_per_coin))
60        .collect::<Vec<Coin>>();
61
62    (coins, asset_ids)
63}
64
65/// Create a vector of UTXOs with the provided AssetIds, num_coins, and amount_per_coin
66pub fn setup_custom_assets_coins(owner: Address, assets: &[AssetConfig]) -> Vec<Coin> {
67    let coins = assets
68        .iter()
69        .flat_map(|asset| {
70            setup_single_asset_coins(owner, asset.id, asset.num_coins, asset.coin_amount)
71        })
72        .collect::<Vec<Coin>>();
73    coins
74}
75
76/// Create a vector of `num_coins` UTXOs containing `amount_per_coin` amount of asset `asset_id`.
77/// The output of this function can be used with `setup_test_provider` to get a client with some
78/// pre-existing coins, but with only one asset ID.
79pub fn setup_single_asset_coins(
80    owner: Address,
81    asset_id: AssetId,
82    num_coins: u64,
83    amount_per_coin: u64,
84) -> Vec<Coin> {
85    let mut rng = rand::thread_rng();
86
87    let coins: Vec<Coin> = (1..=num_coins)
88        .map(|_i| {
89            let mut r = Bytes32::zeroed();
90            r.try_fill(&mut rng)
91                .expect("failed to fill with random data");
92            let utxo_id = UtxoId::new(r, 0);
93
94            Coin {
95                owner,
96                utxo_id,
97                amount: amount_per_coin,
98                asset_id,
99            }
100        })
101        .collect();
102
103    coins
104}
105
106pub fn setup_single_message(
107    sender: Address,
108    recipient: Address,
109    amount: u64,
110    nonce: Nonce,
111    data: Vec<u8>,
112) -> Message {
113    Message {
114        sender,
115        recipient,
116        nonce,
117        amount,
118        data,
119        da_height: 0,
120        status: MessageStatus::Unspent,
121    }
122}
123
124pub async fn setup_test_provider(
125    coins: Vec<Coin>,
126    messages: Vec<Message>,
127    node_config: Option<NodeConfig>,
128    chain_config: Option<ChainConfig>,
129) -> Result<Provider> {
130    let node_config = node_config.unwrap_or_default();
131    let chain_config = chain_config.unwrap_or_else(testnet_chain_config);
132
133    let coin_configs = into_coin_configs(coins);
134    let message_configs = into_message_configs(messages);
135
136    let state_config = StateConfig {
137        coins: coin_configs,
138        messages: message_configs,
139        ..StateConfig::local_testnet()
140    };
141
142    let srv = FuelService::start(node_config, chain_config, state_config).await?;
143
144    let address = srv.bound_address();
145
146    tokio::spawn(async move {
147        let _own_the_handle = srv;
148        let () = futures::future::pending().await;
149    });
150
151    Provider::from(address).await
152}
153
154// Testnet ChainConfig with increased tx size and contract size limits
155fn testnet_chain_config() -> ChainConfig {
156    let mut consensus_parameters = ConsensusParameters::default();
157    let tx_params = TxParameters::default().with_max_size(10_000_000);
158    // on a best effort basis, if we're given an old core we won't fail only because we couldn't
159    // set the limit here
160    let _ = consensus_parameters.set_block_transaction_size_limit(10_000_000);
161
162    let contract_params = ContractParameters::default().with_contract_max_size(1_000_000);
163    consensus_parameters.set_tx_params(tx_params);
164    consensus_parameters.set_contract_params(contract_params);
165
166    ChainConfig {
167        consensus_parameters,
168        ..ChainConfig::local_testnet()
169    }
170}
171
172pub fn generate_random_salt() -> [u8; 32] {
173    StdRng::from_entropy().r#gen()
174}
175
176#[cfg(test)]
177mod tests {
178    use std::net::{Ipv4Addr, SocketAddr};
179
180    use fuel_tx::{ConsensusParameters, ContractParameters, FeeParameters, TxParameters};
181
182    use super::*;
183
184    #[tokio::test]
185    async fn test_setup_single_asset_coins() -> Result<()> {
186        let mut rng = rand::thread_rng();
187        let address = rng.r#gen();
188
189        let mut asset_id = AssetId::zeroed();
190        asset_id
191            .try_fill(&mut rng)
192            .expect("failed to fill with random data");
193
194        let number_of_coins = 11;
195        let amount_per_coin = 10;
196        let coins = setup_single_asset_coins(address, asset_id, number_of_coins, amount_per_coin);
197
198        assert_eq!(coins.len() as u64, number_of_coins);
199        for coin in coins {
200            assert_eq!(coin.asset_id, asset_id);
201            assert_eq!(coin.amount, amount_per_coin);
202            assert_eq!(coin.owner, address);
203        }
204
205        Ok(())
206    }
207
208    #[tokio::test]
209    async fn test_setup_multiple_assets_coins() -> Result<()> {
210        let mut rng = rand::thread_rng();
211        let address = rng.r#gen();
212
213        let number_of_assets = 7;
214        let coins_per_asset = 10;
215        let amount_per_coin = 13;
216        let (coins, unique_asset_ids) = setup_multiple_assets_coins(
217            address,
218            number_of_assets,
219            coins_per_asset,
220            amount_per_coin,
221        );
222
223        assert_eq!(coins.len() as u64, number_of_assets * coins_per_asset);
224        assert_eq!(unique_asset_ids.len() as u64, number_of_assets);
225        // Check that the wallet has base assets to pay for gas
226        assert!(
227            unique_asset_ids
228                .iter()
229                .any(|&asset_id| asset_id == AssetId::zeroed())
230        );
231        for asset_id in unique_asset_ids {
232            let coins_asset_id: Vec<Coin> = coins
233                .clone()
234                .into_iter()
235                .filter(|c| c.asset_id == asset_id)
236                .collect();
237            assert_eq!(coins_asset_id.len() as u64, coins_per_asset);
238            for coin in coins_asset_id {
239                assert_eq!(coin.owner, address);
240                assert_eq!(coin.amount, amount_per_coin);
241            }
242        }
243
244        Ok(())
245    }
246
247    #[tokio::test]
248    async fn test_setup_custom_assets_coins() -> Result<()> {
249        let mut rng = rand::thread_rng();
250        let address = rng.r#gen();
251
252        let asset_base = AssetConfig {
253            id: AssetId::zeroed(),
254            num_coins: 2,
255            coin_amount: 4,
256        };
257
258        let mut asset_id_1 = AssetId::zeroed();
259        asset_id_1
260            .try_fill(&mut rng)
261            .expect("failed to fill with random data");
262        let asset_1 = AssetConfig {
263            id: asset_id_1,
264            num_coins: 6,
265            coin_amount: 8,
266        };
267
268        let mut asset_id_2 = AssetId::zeroed();
269        asset_id_2
270            .try_fill(&mut rng)
271            .expect("failed to fill with random data");
272        let asset_2 = AssetConfig {
273            id: asset_id_2,
274            num_coins: 10,
275            coin_amount: 12,
276        };
277
278        let assets = vec![asset_base, asset_1, asset_2];
279        let coins = setup_custom_assets_coins(address, &assets);
280
281        for asset in assets {
282            let coins_asset_id: Vec<Coin> = coins
283                .clone()
284                .into_iter()
285                .filter(|c| c.asset_id == asset.id)
286                .collect();
287            assert_eq!(coins_asset_id.len() as u64, asset.num_coins);
288            for coin in coins_asset_id {
289                assert_eq!(coin.owner, address);
290                assert_eq!(coin.amount, asset.coin_amount);
291            }
292        }
293        Ok(())
294    }
295
296    #[tokio::test]
297    async fn test_setup_test_provider_custom_config() -> Result<()> {
298        let socket = SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), 4000);
299        let config = NodeConfig {
300            addr: socket,
301            ..NodeConfig::default()
302        };
303
304        let provider = setup_test_provider(vec![], vec![], Some(config.clone()), None).await?;
305        let node_info = provider
306            .node_info()
307            .await
308            .expect("Failed to retrieve node info!");
309
310        assert_eq!(provider.url(), format!("http://127.0.0.1:4000"));
311        assert_eq!(node_info.utxo_validation, config.utxo_validation);
312
313        Ok(())
314    }
315
316    #[tokio::test]
317    async fn test_setup_test_client_consensus_parameters_config() -> Result<()> {
318        let tx_params = TxParameters::default()
319            .with_max_gas_per_tx(2)
320            .with_max_inputs(58);
321        let fee_params = FeeParameters::default().with_gas_per_byte(2);
322        let contract_params = ContractParameters::default().with_max_storage_slots(83);
323
324        let mut consensus_parameters = ConsensusParameters::default();
325        consensus_parameters.set_tx_params(tx_params);
326        consensus_parameters.set_fee_params(fee_params);
327        consensus_parameters.set_contract_params(contract_params);
328
329        let chain_config = ChainConfig {
330            consensus_parameters: consensus_parameters.clone(),
331            ..ChainConfig::default()
332        };
333        let provider = setup_test_provider(vec![], vec![], None, Some(chain_config)).await?;
334
335        let retrieved_parameters = provider.consensus_parameters().await?;
336
337        assert_eq!(retrieved_parameters, consensus_parameters);
338
339        Ok(())
340    }
341
342    #[tokio::test]
343    async fn test_chain_config_and_consensus_parameters() -> Result<()> {
344        let max_inputs = 123;
345        let gas_per_byte = 456;
346
347        let mut consensus_parameters = ConsensusParameters::default();
348
349        let tx_params = TxParameters::default().with_max_inputs(max_inputs);
350        consensus_parameters.set_tx_params(tx_params);
351
352        let fee_params = FeeParameters::default().with_gas_per_byte(gas_per_byte);
353        consensus_parameters.set_fee_params(fee_params);
354
355        let chain_name = "fuel-0".to_string();
356        let chain_config = ChainConfig {
357            chain_name: chain_name.clone(),
358            consensus_parameters,
359            ..ChainConfig::local_testnet()
360        };
361
362        let provider = setup_test_provider(vec![], vec![], None, Some(chain_config)).await?;
363
364        let chain_info = provider.chain_info().await?;
365
366        assert_eq!(chain_info.name, chain_name);
367        assert_eq!(
368            chain_info.consensus_parameters.tx_params().max_inputs(),
369            max_inputs
370        );
371        assert_eq!(
372            chain_info.consensus_parameters.fee_params().gas_per_byte(),
373            gas_per_byte
374        );
375        Ok(())
376    }
377}