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