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