Predicates

Predicates, in Sway, are programs that return a Boolean value and do not have any side effects (they are pure). A predicate address can own assets. The predicate address is generated from the compiled byte code and is the same as the P2SH address used in Bitcoin. Users can seamlessly send assets to the predicate address as they do for any other address. To spend the predicate funds, the user has to provide the original byte code of the predicate together with the predicate data. The predicate data will be used when executing the byte code, and the funds can be transferred if the predicate is validated successfully.

Instantiating predicates

Let's consider the following predicate example:

predicate;

fn main(a: u32, b: u64) -> bool {
    b == a.as_u64()
}

We will look at a complete example of using the SDK to send and receive funds from a predicate.

First, we set up the wallets and a node instance. The call to the abigen! macro will generate all the types specified in the predicate plus two custom structs:

  • an encoder with an encode_data function that will conveniently encode all the arguments of the main function for us.
  • a configurables struct which holds methods for setting all the configurables mentioned in the predicate

Note: The abigen! macro will append Encoder and Configurables to the predicate's name field. Fox example, name="MyPredicate" will result in two structs called MyPredicateEncoder and MyPredicateConfigurables.

        let asset_id = AssetId::zeroed();
        let wallets_config = WalletsConfig::new_multiple_assets(
            2,
            vec![AssetConfig {
                id: asset_id,
                num_coins: 1,
                coin_amount: 1_000,
            }],
        );

        let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?;

        let first_wallet = &wallets[0];
        let second_wallet = &wallets[1];

        abigen!(Predicate(name="MyPredicate", abi="packages/fuels/tests/predicates/basic_predicate/out/debug/basic_predicate-abi.json"));

Once we've compiled our predicate with forc build, we can create a Predicate instance via Predicate::load_from. The resulting data from encode_data can then be set on the loaded predicate.

        let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?;
        let code_path =
            "../../packages/fuels/tests/predicates/basic_predicate/out/debug/basic_predicate.bin";

        let predicate: Predicate = Predicate::load_from(code_path)?
            .with_provider(first_wallet.try_provider()?.clone())
            .with_data(predicate_data);

Next, we lock some assets in this predicate using the first wallet:

        // First wallet transfers amount to predicate.
        first_wallet
            .transfer(predicate.address(), 500, asset_id, TxPolicies::default())
            .await?;

        // Check predicate balance.
        let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;

        assert_eq!(balance, 500);

Then we can transfer assets owned by the predicate via the Account trait:

        let amount_to_unlock = 500;

        predicate
            .transfer(
                second_wallet.address(),
                amount_to_unlock,
                asset_id,
                TxPolicies::default(),
            )
            .await?;

        // Predicate balance is zero.
        let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;

        assert_eq!(balance, 0);

        // Second wallet balance is updated.
        let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?;
        assert_eq!(balance, 1500);

Configurable constants

Same as contracts and scripts, you can define configurable constants in predicates, which can be changed during the predicate execution. Here is an example of how the constants are defined.

#[allow(dead_code)]
enum EnumWithGeneric<D> {
    VariantOne: D,
    VariantTwo: (),
}

struct StructWithGeneric<D> {
    field_1: D,
    field_2: u64,
}

configurable {
    U8: u8 = 8u8,
    BOOL: bool = true,
    STRUCT: StructWithGeneric<u8> = StructWithGeneric {
        field_1: 8u8,
        field_2: 16,
    },
    ENUM: EnumWithGeneric<bool> = EnumWithGeneric::VariantOne(true),
}

fn main(
    u_8: u8,
    switch: bool,
    some_struct: StructWithGeneric<u8>,
    some_enum: EnumWithGeneric<bool>,
) -> bool {
    u_8 == U8 && switch == BOOL && some_struct == STRUCT && some_enum == ENUM
}

Each configurable constant will get a dedicated with method in the SDK. For example, the constant U8 will get the with_U8 method which accepts the same type defined in sway. Below is an example where we chain several with methods and update the predicate with the new constants.

    abigen!(Predicate(
        name = "MyPredicate",
        abi = "packages/fuels/tests/predicates/predicate_configurables/out/debug/predicate_configurables-abi.json"
    ));

    let new_struct = StructWithGeneric {
        field_1: 32u8,
        field_2: 64,
    };
    let new_enum = EnumWithGeneric::VariantTwo;

    let configurables = MyPredicateConfigurables::default()
        .with_STRUCT(new_struct.clone())?
        .with_ENUM(new_enum.clone())?;

    let predicate_data =
        MyPredicateEncoder::default().encode_data(8u8, true, new_struct, new_enum)?;

    let mut predicate: Predicate = Predicate::load_from(
        "tests/predicates/predicate_configurables/out/debug/predicate_configurables.bin",
    )?
    .with_data(predicate_data)
    .with_configurables(configurables);