Skip to content

Commit

Permalink
feat: Demystifying Predicates (#180)
Browse files Browse the repository at this point in the history
* init

* faster CI

* spell check

* intro to predicates

* on to rust testing

* CI fix

* test

* rust tests needs editing before importing into docs

* ci

* testing rust parts

* try again

* pause

* Should be working now

* review time

* again

* predicate update
  • Loading branch information
calldelegation authored Feb 21, 2024
1 parent d56364d commit 4dadebf
Show file tree
Hide file tree
Showing 19 changed files with 1,006 additions and 2 deletions.
1 change: 1 addition & 0 deletions .github/workflows/guides.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
guide:
- "dev quickstart"
- "intro to sway"
- "intro to predicates"

steps:
# SETUP
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
[submodule "docs/guides/examples/intro-to-sway"]
path = docs/guides/examples/intro-to-sway
url = https://github.com/FuelLabs/intro-to-sway.git
[submodule "docs/guides/examples/intro-to-predicates"]
path = docs/guides/examples/intro-to-predicates
url = https://github.com/FuelLabs/intro-to-predicates.git
[submodule "docs/guides/docs/migration-guide/breaking-change-log"]
path = docs/guides/docs/migration-guide/breaking-change-log
url = https://github.com/FuelLabs/breaking-change-log
Expand Down
6 changes: 6 additions & 0 deletions docs/guides/docs/guides.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"featured": false,
"tags": ["Full Stack"]
},
"intro_to_predicates": {
"title": "Building stateless DeFI applications using Predicates",
"description": "Learn about predicates by building a MultiSig",
"featured": false,
"tags": ["Full Stack"]
},
"running_a_node": {
"title": "Running a Node",
"description": "Run a local Fuel node.",
Expand Down
56 changes: 56 additions & 0 deletions docs/guides/docs/intro-to-predicates/checkpoint.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: Checkpoint
category: Intro to Predicates
parent:
label: All Guides
link: /guides
---

# Checkpoint

If you have followed the steps properly, your predicate `main.sw` should look like the code below:

<CodeImport
file="../../examples/intro-to-predicates/multisig-predicate/src/main.sw"
comment="all"
commentType="//"
lang="sway"
/>

## Building the predicate

To format your contract, execute the command:

<TestAction
id="format-predicate"
action={{
name: 'runCommand',
commandFolder: 'guides-testing/multisig-predicate/predicate'
}}
/>

```sh
forc fmt
```

To get the predicate root, go to the predicate folder and run:

<TestAction
id="build-predicate"
action={{
name: 'runCommand',
commandFolder: 'guides-testing/multisig-predicate/predicate'
}}
/>

```sh
forc build
```

Your predicate root should be exactly:

```sh
0x9cdce04cdb323e5982bbd0c07f667c6ea2b97781a8ce6f3e2d96c0e1b5acde73
```

That's it! You've created your first **stateless** decentralized application, and we didn't even have to deploy it!
30 changes: 30 additions & 0 deletions docs/guides/docs/intro-to-predicates/configurables.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: Configurables
category: Intro to Predicates
parent:
label: All Guides
link: /guides
---

# Configurables

Configurables are special constants that can be modified at compile time. This is where we can define the signers responsible for protecting the funds in the predicate as well as the number of signatures required.

This information can later be configured with SDKs before building a transaction.

<TestAction
id="sway-configurable"
action={{
name: 'modifyFile',
filepath: 'guides-testing/multisig-predicate/predicate/src/main.sw'
}}
/>

<CodeImport
file="../../examples/intro-to-predicates/multisig-predicate/src/main.sw"
comment="configurable"
commentType="//"
lang="sway"
/>

Imagine you are a multisig provider assisting businesses and users in setting up their own multisigs. You wouldn't want to hard-code these details every time but rather provide a few parameters that users can configure themselves.
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
title: Logging in Rust tests
category: Intro to Predicates
parent:
label: All Guides
link: /guides
---

# Logging in Rust tests

## Generating a Test Template in Rust

To create your own test template using Rust, follow these steps with `cargo-generate` in the script project directory:

1. Install `cargo-generate`:

```bash
cargo install cargo-generate --locked
```

{/*markdownlint-disable*/}
2. Generate the template:
{/*markdownlint-disable*/}

<TestAction
id="cargo-generate-test"
action={{
name: 'runCommand',
commandFolder: 'guides-testing/predicate-script-logging/'
}}
/>

```bash
cargo generate --init fuellabs/sway templates/sway-test-rs --name sway-store
```

## Logging

We previously covered imports and setting up the predicate in an earlier introduction to Sway tutorial, specifically in the [Rust testing section](https://docs.fuel.network/guides/intro-to-sway/rust-sdk/). If you haven't checked that out yet, I highly recommend doing so.

Copy and paste the rust test below:

<TestAction
id="sway-program-type"
action={{
name: 'writeToFile',
filepath: 'guides-testing/predicate-script-logging/tests/harness.rs'
}}
/>

<CodeImport
file="../../examples/intro-to-predicates/predicate-script-logging/tests/harness.rs"
comment="all"
commentType="//"
lang="sway"
/>

Now, I want to draw your attention to a specific portion of the code here:

<CodeImport
file="../../examples/intro-to-predicates/predicate-script-logging/tests/harness.rs"
comment="logs"
commentType="//"
lang="sway"
/>

We can now call `decode_logs` to extract our secret number, something we weren't able to do when testing with predicates.

To enable print outputs to appear in the console during tests, you can use the `nocapture` flag.

<TestAction
id="cargo-test"
action={{
name: 'runCommand',
commandFolder: 'guides-testing/predicate-script-logging/'
}}
/>

```sh
cargo test -- --nocapture
```

Remembering this method is essential when developing more complex predicates, especially as debugging becomes increasingly challenging.
84 changes: 84 additions & 0 deletions docs/guides/docs/intro-to-predicates/debugging-with-scripts.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: Debugging with Scripts
category: Intro to Predicates
parent:
label: All Guides
link: /guides
---

# Debugging with Scripts

In every aspect of development, trade-offs are inevitable. As previously mentioned, logging is not feasible when dealing with predicates, since predicates are required to be pure. This raises an important question: how do we debug predicates?

Sway, a programming language, categorizes programs into four types, with scripts being one of them. Unlike predicates, scripts allow for shared logic.

Let's move outside our MultiSig project

```sh
cd ../..
```

and create a separate project called `predicate-script-logging`.

<TestAction
id="create-predicate-script-logging"
action={{
name: 'runCommand',
commandFolder: 'guides-testing/'
}}
/>

```sh
forc new --predicate predicate-script-logging
```

Copy and paste this new predicate in your `src/main.sw`. Attempting to build this predicate will result in an error, indicating that logging is an invalid operation.

<CodeImport
file="../../examples/intro-to-predicates/predicate-test-example/src/main.sw"
comment="all"
commentType="//"
lang="sway"
/>

However, let's try switching the program type from a `predicate` to a `script`.

<CodeImport
file="../../examples/intro-to-predicates/predicate-script-logging/src/main.sw"
comment="program_type"
commentType="//"
lang="sway"
/>

Your code should now look like this:

<TestAction
id="sway-program-type"
action={{
name: 'writeToFile',
filepath: 'guides-testing/predicate-script-logging/src/main.sw'
}}
/>

<CodeImport
file="../../examples/intro-to-predicates/predicate-script-logging/src/main.sw"
comment="all"
commentType="//"
lang="sway"
/>

Now, if we attempt to build our script, it should compile without any issues.

<TestAction
id="build-predicate"
action={{
name: 'runCommand',
commandFolder: 'guides-testing/predicate-script-logging/'
}}
/>

```sh
forc build
```

Next, we'll generate a Rust template to see it in action!
97 changes: 97 additions & 0 deletions docs/guides/docs/intro-to-predicates/imports.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
title: Imports
category: Intro to Predicates
parent:
label: All Guides
link: /guides
---

# Imports

The predicate keyword is used to identify that the program is a predicate.

<TestAction
id="sway-program-type"
action={{
name: 'writeToFile',
filepath: 'guides-testing/multisig-predicate/predicate/src/main.sw'
}}
/>

<CodeImport
file="../../examples/intro-to-predicates/multisig-predicate/src/main.sw"
comment="predicate"
commentType="//"
lang="sway"
/>

We're going to utilize the [Sway standard library](https://github.com/FuelLabs/sway/tree/master/sway-lib-std) in our predicate. Delete the template code except for the predicate keyword and copy in the imports below:

<TestAction
id="sway-import"
action={{
name: 'modifyFile',
filepath: 'guides-testing/multisig-predicate/predicate/src/main.sw'
}}
/>

<CodeImport
file="../../examples/intro-to-predicates/multisig-predicate/src/main.sw"
comment="import_parent"
commentType="//"
lang="sway"
/>

## Transactions

To construct the MultiSig, it's essential for us to obtain three specific components from the transaction through the standard library:

<CodeImport
file="../../examples/intro-to-predicates/multisig-predicate/src/main.sw"
comment="import_tx"
commentType="//"
lang="sway"
/>

1. Transaction Witness Data: We'll use this to attach signatures on the transaction.
2. Transaction Witness Count: This will help us determine the number of signatures attached.
3. Transaction ID: The hash of the transaction.

## Constants

From the constants library, we'll be using `ZERO_B256` as a placeholder.

<CodeImport
file="../../examples/intro-to-predicates/multisig-predicate/src/main.sw"
comment="import_zero_b256"
commentType="//"
lang="sway"
/>

## Signatures

We'll need `b512` because signatures are of type `b512`.

<CodeImport
file="../../examples/intro-to-predicates/multisig-predicate/src/main.sw"
comment="import_b512"
commentType="//"
lang="sway"
/>

## Elliptical Curve

Lastly, we will be using `ec_recover_address`, short for elliptical curve recovery address. It's a function that allows us to cryptographically recover the address that signed a piece of data:

```rust
signing_address = ec_recover_address(signed_data, original_data)
```

This step is crucial for safeguarding the funds and ensuring that only the correct wallets can provide the necessary signatures.

<CodeImport
file="../../examples/intro-to-predicates/multisig-predicate/src/main.sw"
comment="import_ecr"
commentType="//"
lang="sway"
/>
Loading

0 comments on commit 4dadebf

Please sign in to comment.