Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add predicates abigen #726

Merged
merged 20 commits into from
Dec 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,16 @@ jobs:

- name: Install forc and forc-fmt
run: |
if [[ -z $FORC_PATCH_BRANCH && -z $FORC_PATCH_REVISION ]]; then
if [[ -n $FORC_PATCH_BRANCH ]]; then
cargo install forc forc-fmt --git https://github.com/FuelLabs/sway --branch $FORC_PATCH_BRANCH
elif [[ -n $FORC_PATCH_BRANCH ]]; then
cargo install forc forc-fmt --git https://github.com/FuelLabs/sway --rev $FORC_PATCH_REVISION
else
curl -sSLf https://github.com/FuelLabs/sway/releases/download/v${{ env.FORC_VERSION }}/forc-binaries-linux_amd64.tar.gz -L -o forc.tar.gz
tar -xvf forc.tar.gz
chmod +x forc-binaries/forc
mv forc-binaries/forc /usr/local/bin/forc
mv forc-binaries/forc-fmt /usr/local/bin/forc-fmt
else
if [[ -n $FORC_PATCH_BRANCH ]]; then
cargo install forc forc-fmt --git https://github.com/FuelLabs/sway --branch $FORC_PATCH_BRANCH
else
cargo install forc forc-fmt --git https://github.com/FuelLabs/sway --rev $FORC_PATCH_REVISION
fi
fi

- name: Build Sway test projects
Expand Down
2 changes: 1 addition & 1 deletion docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
- [Estimating cost](./calling-contracts/cost-estimation.md)
- [Running scripts](./getting-started/running-scripts.md)
- [Predicates](./getting-started/predicates.md)
- [Send and spend funds](./predicates/send-spend-predicate.md)
- [Predicate data](./predicates/predicate-data.md)
- [Signatures example](./predicates/send-spend-predicate.md)
- [Scripts](./scripts/index.md)
- [Running scripts](./scripts/scripts.md)
- [Types](./getting-started/types.md)
Expand Down
6 changes: 3 additions & 3 deletions docs/src/getting-started/predicates.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ Predicates, in Sway, are programs that return a Boolean value, and they do not h

## Instantiating predicates

Similar to contracts, once you've written a predicate in Sway and compiled it with `forc build` (read [here](https://fuellabs.github.io/sway/master/introduction/sway_quickstart.html) for more on how to work with Sway), you'll get the predicate binary. Using the binary, you can instantiate a `predicate` as shown in the code snippet below:
Once you've written a predicate in Sway and compiled it with `forc build`, you can use the `predicate_abigen!` to generate all the types specified in the predicate. Additionally, you will get a `predicate` instance with methods for receiving and spending funds and encoding the predicate data. The code snippet below shows how to use the abigen and generate a predicate instance.

```rust,ignore
{{#include ../../../examples/predicates/src/lib.rs:predicate_load_from}}
{{#include ../../../examples/predicates/src/lib.rs:predicate_load}}
```
The created `predicate` instance has two fields. The predicate `byte code` and the predicate `address`. This address is generated from the 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 on the chain. 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 if the predicate is validated successfully, the funds will be accessible.
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 on the chain. 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 if the predicate is validated successfully, the funds can be transferred.

In the next section, we show how to interact with a predicate and explore an example where specific signatures are needed to spend the predicate funds.

15 changes: 5 additions & 10 deletions docs/src/predicates/predicate-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@
Let's consider the following predicate example:

```rust,ignore
{{#include ../../../packages/fuels/tests/predicates/predicate_data_example/src/main.sw}}
{{#include ../../../packages/fuels/tests/predicates/predicate_basic/src/main.sw}}
```

With the Fuel Rust SDK, You can encode and send the predicate data through `Predicate`'s `encode_data()`:
Similarly to contracts and scripts, the `predicate_abigen!` generates a function that will conveniently encode all the arguments of the main function for us. This function is called `encode_data`, and it is accessed through the predicate instance as shown below:

```rust,ignore
{{#include ../../../examples/predicates/src/lib.rs:encode_predicate_data}}
```

Keep on reading for the full example.
Next, we will look at a complete example of using the SDK to send and receive funds from a predicate.

Notice how this predicate uses `input_predicate_data()`, a way for the predicate code to read the data the caller passed to it.

Like everything else in the FuelVM, this data follows the ABI encoding/decoding specification. When using the Fuel Rust SDK to pass data to this predicate, you must encode it properly.

Here's how you can do it. First, we set up the wallets, node, and predicate code:
First, we set up the wallets, node, and a predicate instance:

```rust,ignore
{{#include ../../../examples/predicates/src/lib.rs:predicate_data_setup}}
Expand All @@ -32,10 +29,8 @@ Next, we lock some assets in this predicate using the first wallet:

Then, we try to unlock the amount and spend it using the second wallet, effectively sending the previously locked value to itself.

The predicate expects the data sent to it to be a `u64` type with the value `42`.
The predicate expects the data sent to it to be two numbers (`u32` and `u64`) with matching values.

```rust,ignore
{{#include ../../../examples/predicates/src/lib.rs:predicate_data_unlock}}
```

> **Note:** if the data you're encoding is already a `Vec<u8>`, e.g., in the [send and spend examples](./send-spend-predicate.md), then you don't need to call `encode_predicate_data()`, passing it as-is works.
16 changes: 8 additions & 8 deletions docs/src/predicates/send-spend-predicate.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Send and spend funds from predicates
# Signatures in predicates example

This is a more involved example where the predicate accepts three signatures and matches them to three predefined public keys. The `ec_recover_address` function is used to recover the public key from the signatures. If two of the three extracted public keys match the predefined public keys, the funds can be spent. Note that the signature order has to match the order of the predefined public keys.

Let's consider the following predicate example:

```rust,ignore
{{#include ../../../packages/fuels/tests/predicates/predicate_signatures/src/main.sw}}
```

This predicate accepts three signatures and matches them to three predefined public keys. The `ec_recover_address` function is used to recover the public key from the signatures. If two of three extracted public keys match the predefined public keys, the funds can be spent. Note that the signature order has to match the order of the predefined public keys.

Let's use the SDK to interact with the predicate. First, let's create three wallets with specific keys. Their hashed public keys are already hard-coded in the predicate.
Let's use the SDK to interact with the predicate. First, let's create three wallets with specific keys. Their hashed public keys are already hard-coded in the predicate. Then we create the receiver wallet, which we will use to spend the predicate funds.

```rust,ignore
{{#include ../../../examples/predicates/src/lib.rs:predicate_wallets}}
Expand All @@ -20,16 +20,16 @@ Next, let's add some coins, start a provider and connect it with the wallets.
{{#include ../../../examples/predicates/src/lib.rs:predicate_coins}}
```

Now we can load the predicate binary, and prepare some transaction variables.
Now we can use the predicate abigen, which will create a predicate instance for us.

```rust,ignore
{{#include ../../../examples/predicates/src/lib.rs:predicate_load}}
```

After the predicate address is generated we can send funds to it. Note that we are using the same `transfer` function as we used when sending funds to other wallets. We also make sure that the funds are indeed transferred.
After the predicate instance is generated we can use the `receive` function to transfer funds to the predicate. We also make sure that the funds are indeed transferred.

```rust,ignore
{{#include ../../../examples/predicates/src/lib.rs:predicate_send}}
{{#include ../../../examples/predicates/src/lib.rs:predicate_receive}}
```

To spend the funds that are now locked in the predicate, we have to provide two out of three signatures whose public keys match the ones we defined in the predicate. In this example, the signatures are generated from an array of zeros.
Expand All @@ -38,7 +38,7 @@ To spend the funds that are now locked in the predicate, we have to provide two
{{#include ../../../examples/predicates/src/lib.rs:predicate_signatures}}
```

After generating the signatures, we can send a transaction to spend the predicate funds. We use the `receiver` wallet as the recipient. We have to provide the predicate byte code and the required signatures. As we provide the correct data, we receive the funds and verify that the amount is correct.
After generating the signatures, we can use the predicate's `encode_data` and `spend` functions to spend the funds. If the provided data is correct the `receiver` wallet will get the funds, and we will verify that the amount is indeed correct.

```rust,ignore
{{#include ../../../examples/predicates/src/lib.rs:predicate_spend}}
Expand Down
157 changes: 68 additions & 89 deletions examples/predicates/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ mod tests {

#[tokio::test]
async fn predicate_example() -> Result<(), Error> {
use fuels::contract::predicate::Predicate;
use fuels::prelude::*;
use fuels::signers::fuel_crypto::SecretKey;

// ANCHOR: predicate_wallets
Expand All @@ -28,89 +26,86 @@ mod tests {
let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None);
let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None);
let mut wallet3 = WalletUnlocked::new_from_private_key(secret_key3, None);
let receiver = WalletUnlocked::new_random(None);
let mut receiver = WalletUnlocked::new_random(None);
// ANCHOR_END: predicate_wallets

// ANCHOR: predicate_coins
let all_coins = [&wallet, &wallet2, &wallet3]
let asset_id = AssetId::default();
let num_coins = 32;
let amount = 64;
let initial_balance = amount * num_coins;
let all_coins = [&wallet, &wallet2, &wallet3, &receiver]
.iter()
.flat_map(|wallet| {
setup_single_asset_coins(wallet.address(), AssetId::default(), 10, 1_000_000)
setup_single_asset_coins(wallet.address(), asset_id, num_coins, amount)
})
.collect::<Vec<_>>();

let (provider, _) = setup_test_provider(
all_coins,
vec![],
Some(Config {
utxo_validation: true,
..Config::local_node()
}),
None,
)
.await;

[&mut wallet, &mut wallet2, &mut wallet3]
let (provider, _) = setup_test_provider(all_coins, vec![], None, None).await;

[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
.for_each(|wallet| wallet.set_provider(provider.clone()));
// ANCHOR_END: predicate_coins

// ANCHOR: predicate_load
// ANCHOR: predicate_load_from
let predicate = Predicate::load_from(
predicate_abigen!(
MyPredicate,
"packages/fuels/tests/predicates/predicate_signatures/out/debug/predicate_signatures-abi.json"
);

let predicate = MyPredicate::load_from(
"../../packages/fuels/tests/predicates/predicate_signatures/out/debug/predicate_signatures.bin",
)?;

let predicate_code = predicate.code();
let predicate_address = predicate.address();
// ANCHOR_END: predicate_load_from
let amount_to_predicate = 1000;
let asset_id = AssetId::default();
// ANCHOR_END: predicate_load

// ANCHOR: predicate_send
wallet
.transfer(
predicate_address,
amount_to_predicate,
asset_id,
TxParameters::default(),
)
// ANCHOR: predicate_receive
let amount_to_predicate = 512;

predicate
.receive(&wallet, amount_to_predicate, asset_id, None)
.await?;

let predicate_balance = provider
.get_asset_balance(predicate.address(), asset_id)
.await?;
assert_eq!(predicate_balance, amount_to_predicate);
// ANCHOR_END: predicate_send
// ANCHOR_END: predicate_receive

// ANCHOR: predicate_signatures
let data_to_sign = [0; 32];
let signature1 = wallet.sign_message(data_to_sign).await?.to_vec();
let signature2 = wallet2.sign_message(data_to_sign).await?.to_vec();
let signature3 = wallet3.sign_message(data_to_sign).await?.to_vec();

let signatures = vec![signature1, signature2, signature3];
let signature1: B512 = wallet
.sign_message(data_to_sign)
.await?
.as_ref()
.try_into()?;
let signature2: B512 = wallet2
.sign_message(data_to_sign)
.await?
.as_ref()
.try_into()?;
let signature3: B512 = wallet3
.sign_message(data_to_sign)
.await?
.as_ref()
.try_into()?;

let signatures = [signature1, signature2, signature3];
// ANCHOR_END: predicate_signatures

// ANCHOR: predicate_spend
let predicate_data = signatures.into_iter().flatten().collect();
wallet
.spend_predicate(
predicate_address,
predicate_code,
amount_to_predicate,
asset_id,
receiver.address(),
Some(predicate_data),
TxParameters::default(),
)
predicate
.encode_data(signatures)
.spend(&receiver, amount_to_predicate, asset_id, None)
.await?;

let receiver_balance_after = provider
.get_asset_balance(receiver.address(), asset_id)
.await?;
assert_eq!(amount_to_predicate, receiver_balance_after);
assert_eq!(
initial_balance + amount_to_predicate,
receiver_balance_after
);

let predicate_balance = provider
.get_asset_balance(predicate.address(), asset_id)
Expand All @@ -124,79 +119,63 @@ mod tests {
#[tokio::test]
async fn predicate_data_example() -> Result<(), Error> {
// ANCHOR: predicate_data_setup
let provider_config = Config {
utxo_validation: true,
..Config::local_node()
};

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

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

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

let predicate = Predicate::load_from( "../../packages/fuels/tests/predicates/predicate_data_example/out/debug/predicate_data_example.bin")?;
predicate_abigen!(
MyPredicate,
"packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic-abi.json"
);

let predicate_code = predicate.code();
let predicate_address = predicate.address();
let predicate = MyPredicate::load_from(
"../../packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic.bin",
)?;
// ANCHOR_END: predicate_data_setup

// ANCHOR: predicate_data_lock_amount
// First wallet transfers amount to predicate.
let _result = first_wallet
.transfer(
predicate_address,
500,
AssetId::default(),
TxParameters::default(),
)
.await?;
predicate.receive(first_wallet, 500, asset_id, None).await?;

// Check predicate balance.
let balance = first_wallet
.get_provider()?
.get_asset_balance(predicate_address, AssetId::default())
.get_asset_balance(predicate.address(), asset_id)
.await?;

assert_eq!(balance, 500);
// ANCHOR_END: predicate_data_lock_amount

// ANCHOR: predicate_data_unlock
// We use the Predicate's `encode_data()` to encode the data we want to
// send to the predicate.

//
// ANCHOR: encode_predicate_data
let predicate_data: Vec<u8> = predicate.encode_data(42_u64)?;
let predicate = predicate.encode_data(4096, 4096);
// ANCHOR_END: encode_predicate_data

// ANCHOR: predicate_data_unlock
// We use the Predicate's `encode_data()` to encode the data we want to
// send to the predicate. This is a builder pattern and the function
// returns a new predicate.
let amount_to_unlock = 500;

let _result = second_wallet
.spend_predicate(
predicate_address,
predicate_code,
amount_to_unlock,
AssetId::default(),
second_wallet.address(),
Some(predicate_data),
TxParameters::default(),
)
predicate
.encode_data(4096, 4096)
.spend(second_wallet, amount_to_unlock, asset_id, None)
.await?;

// Predicate balance is zero.
let balance = first_wallet
.get_provider()?
.get_asset_balance(predicate_address, AssetId::default())
.get_asset_balance(predicate.address(), AssetId::default())
.await?;

assert_eq!(balance, 0);
Expand Down
Loading