The setup_program_test! macro

When deploying contracts with the abigen! macro, as shown in the previous sections, the user can:

  • change the default configuration parameters
  • launch several providers
  • create multiple wallets
  • create specific assets, etc.

However, it is often the case that we want to quickly set up a test with default values and work directly with contract or script instances. The setup_program_test! can do exactly that.


Used to reduce boilerplate in integration tests. Accepts input in the form of COMMAND(ARG...)...

COMMAND is either Wallets, Abigen, LoadScript or Deploy.

ARG is either a:

  • name-value (e.g. name="MyContract"), or,
  • a literal (e.g. "some_str_literal", true, 5, ...)
  • a sub-command (e.g. Abigen(Contract(name="MyContract", project="some_project")))

Available COMMANDs:

Wallets

Example: Wallets("a_wallet", "another_wallet"...)

Description: Launches a local provider and generates wallets with names taken from the provided ARGs.

Cardinality: 0 or 1.

Abigen

Example:

Abigen(
    Contract(
        name = "MyContract",
        project = "some_folder"
    ),
    Script(
        name = "MyScript",
        project = "some_folder"
    ),
    Predicate(
        name = "MyPredicate",
        project = "some_folder"
    ),
)

Description: Generates the program bindings under the name name. project should point to root of the forc project.

Cardinality: 0 or N.

Deploy

Example: Deploy(name="instance_name", contract="MyContract", wallet="a_wallet")

Description: Deploys the contract (with salt) using wallet. Will create a contract instance accessible via name. Due to salt usage, the same contract can be deployed multiple times. Requires that an Abigen command be present with name equal to contract. wallet can either be one of the wallets in the Wallets COMMAND or the name of a wallet you've previously generated yourself.

Cardinality: 0 or N.

LoadScript

Example: LoadScript(name = "script_instance", script = "MyScript", wallet = "wallet")

Description: Creates a script instance of script under name using wallet.

Cardinality: 0 or N.


The setup code that you have seen in previous sections gets reduced to:

        setup_program_test!(
            Wallets("wallet"),
            Abigen(Contract(
                name = "TestContract",
                project = "packages/fuels/tests/contracts/contract_test"
            )),
            Deploy(
                name = "contract_instance",
                contract = "TestContract",
                wallet = "wallet"
            ),
        );

        let response = contract_instance
            .methods()
            .initialize_counter(42)
            .call()
            .await?;

        assert_eq!(42, response.value);

Note The same contract can be deployed several times as the macro deploys the contracts with salt. You can also deploy different contracts to the same provider by referencing the same wallet in the Deploy command.

    setup_program_test!(
        Wallets("wallet"),
        Abigen(
            Contract(
                name = "LibContract",
                project = "packages/fuels/tests/contracts/lib_contract"
            ),
            Contract(
                name = "LibContractCaller",
                project = "packages/fuels/tests/contracts/lib_contract_caller"
            ),
        ),
        Deploy(
            name = "lib_contract_instance",
            contract = "LibContract",
            wallet = "wallet"
        ),
        Deploy(
            name = "contract_caller_instance",
            contract = "LibContractCaller",
            wallet = "wallet"
        ),
        Deploy(
            name = "contract_caller_instance2",
            contract = "LibContractCaller",
            wallet = "wallet"
        ),
    );
    let lib_contract_id = lib_contract_instance.contract_id();

    let contract_caller_id = contract_caller_instance.contract_id();

    let contract_caller_id2 = contract_caller_instance2.contract_id();

    // Because we deploy with salt, we can deploy the same contract multiple times
    assert_ne!(contract_caller_id, contract_caller_id2);

    // The first contract can be called because they were deployed on the same provider
    let response = contract_caller_instance
        .methods()
        .increment_from_contract(lib_contract_id, 42)
        .with_contracts(&[&lib_contract_instance])
        .call()
        .await?;

    assert_eq!(43, response.value);

    let response = contract_caller_instance2
        .methods()
        .increment_from_contract(lib_contract_id, 42)
        .with_contracts(&[&lib_contract_instance])
        .call()
        .await?;

    assert_eq!(43, response.value);

In this example, three contracts are deployed on the same provider using the wallet generated by the Wallets command. The second and third macros use the same contract but have different IDs because of the deployment with salt. Both of them can call the first contract by using their ID.

In addition, you can manually create the wallet variable and then use it inside the macro. This is useful if you want to create custom wallets or providers but still want to use the macro to reduce boilerplate code. Below is an example of this approach.

    let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));

    let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
    let wallet = wallets.pop().unwrap();
    let wallet_2 = wallets.pop().unwrap();

    setup_program_test!(
        Abigen(Contract(
            name = "TestContract",
            project = "packages/fuels/tests/contracts/contract_test"
        )),
        Deploy(
            name = "contract_instance",
            contract = "TestContract",
            wallet = "wallet"
        ),
    );