Skip to content

Commit

Permalink
feat!: new abigen!, setup_contract_test!, and handling of shared …
Browse files Browse the repository at this point in the history
…types (#763)

`abigen!` can now generate contracts, scripts or predicates depending on its input. Multiple bindings can be generated at once, identical types are shared between the bindings.

`setup_contract_test!` is also adapted to conform better to `abigen!` changes.
  • Loading branch information
segfault-magnet authored Jan 16, 2023
1 parent 34f0ec4 commit a8c4586
Show file tree
Hide file tree
Showing 121 changed files with 6,219 additions and 2,115 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
# https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html#details
resolver = "2"
members = [
"examples/abigen",
"examples/contracts",
"examples/cookbook",
"examples/debugging",
"examples/predicates",
"examples/providers",
"examples/rust_bindings",
"examples/types",
"examples/wallets",
"examples/debugging",
"packages/fuels",
"packages/fuels-abigen-macro",
"packages/fuels-contract",
Expand All @@ -22,7 +23,7 @@ members = [
"packages/fuels-test-helpers",
"packages/fuels-types",
"packages/wasm-tests",
"scripts/test-projects",
"scripts/check-docs",
"scripts/test-projects",
"tools/fuels-abi-cli",
]
7 changes: 4 additions & 3 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
- [Setting up test wallets](./wallets/test-wallets.md)
- [Signing](./wallets/signing.md)
- [Transferring assets](./wallets/transferring-assets.md)
- [Generating bindings with `abigen!`](./getting-started/abigen.md)
- [The JSON ABI file](abigen/the-json-abi-file.md)
- [The `abigen!` macro](abigen/the-abigen-macro.md)
- [Deploying contracts](./getting-started/contracts.md)
- [Interacting with contracts](./contracts/interacting-with-contracts.md)
- [The FuelVM Binary file](./contracts/the-fuelvm-binary-file.md)
- [The JSON ABI file](./contracts/the-json-abi-file.md)
- [The `abigen!` macro](./contracts/the-abigen-macro.md)
- [The `setup_contract_test!` macro](./contracts/the-setup-contract-test-macro.md)
- [Calling contracts](./getting-started/calling-contracts.md)
- [Connecting wallets](./calling-contracts/calls-with-different-wallets.md)
- [Transaction parameters](./calling-contracts/tx-params.md)
Expand Down Expand Up @@ -61,6 +61,7 @@
- [The Function selector](./debugging/function-selector.md)
- [Testing](./testing/index.md)
- [Tweaking the blockchain](./testing/chains.md)
- [The `setup_contract_test!` macro](testing/the-setup-contract-test-macro.md)
- [Cookbook](./cookbook/cookbook.md)
- [Custom consensus parameters](./cookbook/custom-chain.md)
- [Deposit and Withdraw](./cookbook/deposit-and-withdraw.md)
Expand Down
96 changes: 96 additions & 0 deletions docs/src/abigen/the-abigen-macro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# abigen!

`abigen!` is a procedural macro -- it generates code. It accepts inputs in the format of:
```text
ProgramType(name="MyProgramType", abi="my_program-abi.json")...
```
where:

- `ProgramType` is one of: `Contract`, `Script` or `Predicate`,

- `name` is the name that will be given to the generated bindings,

- `abi` is either a path to the json abi file or its actual contents.

---
So, an `abigen!` which generates bindings for two contracts and one script looks like this:
```rust,ignore
{{#include ../../../examples/abigen/src/lib.rs:multiple_abigen_program_types}}
```

## How does the generated code look?

A rough overview:
```rust,ignore
pub mod abigen_bindings {
pub mod contract_a_mod {
struct SomeCustomStruct{/*...*/};
// other custom types used in the contract
struct ContractA {/*...*/};
impl ContractA {/*...*/};
// ...
}
pub mod contract_b_mod {
// ...
}
pub mod my_script_mod {
// ...
}
pub mod my_predicate_mod{
// ...
}
pub mod shared_types{
// ...
}
}
pub use contract_a_mod::{/*..*/};
pub use contract_b_mod::{/*..*/};
pub use my_predicate_mod::{/*..*/};
pub use shared_types::{/*..*/};
```

Each `ProgramType` gets its own `mod` based on the `name` given in the `abigen!`. Inside the respective mods, the custom types used by that program are generated, and the bindings through which the actual calls can be made.

One extra `mod` called `shared_types` is generated if `abigen!` detects that the given programs share types. Instead of each `mod` regenerating the type for itself, the type is lifted out into the `shared_types` module, generated only once, and then shared between all programs that use it.

A type is deemed shared if its name and definition match up. This can happen either because you've used the same library (a custom one or a type from the stdlib) or because you've happened to define the exact same type.

Finally, `pub use` statements are inserted, so you don't have to fully qualify the generated types. To avoid conflict, only types that have unique names will get a `pub use` statement. If you find rustc can't find your type, it might just be that there is another generated type with the same name. To fix the issue just qualify the path by doing `abigen_bindings::whatever_contract_mod::TheType`.

> **Note:**
> It is **highly** encouraged that you generate all your bindings in one `abigen!` call. Doing it in this manner will allow type sharing and avoid name collisions you'd normally get when calling `abigen!` multiple times inside the same namespace. If you choose to proceed otherwise, keep in mind the generated code overview presented above and appropriately separate the `abigen!` calls into different modules to resolve the collision.
## Using the bindings
Let's look at a contract with two methods: `initialize_counter(arg: u64) -> u64` and `increment_counter(arg: u64) -> u64`, with the following JSON ABI:

```json,ignore
{{#include ../../../examples/rust_bindings/src/abi.json}}
```

By doing this:
```rust,ignore
{{#include ../../../examples/rust_bindings/src/lib.rs:use_abigen}}
```

or this:

```rust,ignore
{{#include ../../../examples/rust_bindings/src/lib.rs:abigen_with_string}}
```


you'll generate this (shortened for brevity's sake):

```rust,ignore
{{#include ../../../examples/rust_bindings/src/rust_bindings_formatted.rs}}
```

> **Note:** that is all **generated** code. No need to write any of that. Ever. The generated code might look different from one version to another, this is just an example to give you an idea of what it looks like.
Then, you're able to use it to call the actual methods on the deployed contract:

```rust,ignore
{{#include ../../../examples/contracts/src/lib.rs:use_deployed_contract}}
```
File renamed without changes.
46 changes: 0 additions & 46 deletions docs/src/contracts/the-abigen-macro.md

This file was deleted.

37 changes: 0 additions & 37 deletions docs/src/contracts/the-setup-contract-test-macro.md

This file was deleted.

12 changes: 12 additions & 0 deletions docs/src/getting-started/abigen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Generating bindings with abigen!

You might have noticed this snippet in the previous sections:

```rust,ignore
{{#include ../../../examples/contracts/src/lib.rs:abigen_example}}
```

The SDK lets you transform ABI methods of a smart contract, specified as JSON objects (which you can get from [Forc](https://github.com/FuelLabs/sway/tree/master/forc)), into Rust structs and methods that are type-checked at compile time.
In order to call your contracts, scripts or predicates, you first need to generate the Rust bindings for them.

The following subsections contain more details about the `abigen!` syntax and the code generated from it.
2 changes: 1 addition & 1 deletion docs/src/getting-started/api.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# API

For a more in-depth look at the APIs provided by the Fuel Rust SDK, head over to the [official documentation](https://docs.rs/fuels/latest/fuels/). In the actual rust docs, you can see the most up-to-date information about the API, which is synced with the code as it changes.
For a more in-depth look at the APIs provided by the Fuel Rust SDK, head over to the [official documentation](https://docs.rs/fuels/latest/fuels/). In the actual Rust docs, you can see the most up-to-date information about the API, which is synced with the code as it changes.
2 changes: 1 addition & 1 deletion docs/src/getting-started/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ There are two main ways of working with contracts in the SDK: deploying a contra

Once you've written a contract 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 have in your hands two important artifacts: the compiled binary file and the JSON ABI file.

Below is how you can deploy your contracts using the SDK. For more details about each component in this process, read [The abigen macro](../contracts/the-abigen-macro.md), [The FuelVM binary file](../contracts/the-fuelvm-binary-file.md), and [The JSON ABI file](../contracts/the-json-abi-file.md).
Below is how you can deploy your contracts using the SDK. For more details about each component in this process, read [The abigen macro](../abigen/the-abigen-macro.md), [The FuelVM binary file](../contracts/the-fuelvm-binary-file.md), and [The JSON ABI file](../abigen/the-json-abi-file.md).

### The deploy functions

Expand Down
2 changes: 1 addition & 1 deletion docs/src/getting-started/predicates.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Predicates, in Sway, are programs that return a Boolean value, and they do not h

## Instantiating predicates

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.
Once you've written a predicate in Sway and compiled it with `forc build`, you can use the `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}}
Expand Down
2 changes: 1 addition & 1 deletion docs/src/getting-started/running-scripts.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Running scripts

You can run a script using its JSON-ABI and the path to its binary file. You can run the scripts with arguments. For this, you have to use the `script_abigen!` macro, which is similar to the `abigen!` macro seen [previously](../contracts/the-abigen-macro.md).
You can run a script using its JSON-ABI and the path to its binary file. You can run the scripts with arguments. For this, you have to use the `abigen!` macro seen [previously](../abigen/the-abigen-macro.md).

````rust,ignore
{{#include ../../../packages/fuels/tests/scripts.rs:script_with_arguments}}
Expand Down
2 changes: 1 addition & 1 deletion docs/src/predicates/predicate-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Let's consider the following predicate example:
{{#include ../../../packages/fuels/tests/predicates/predicate_basic/src/main.sw}}
```

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:
Similarly to contracts and scripts, the `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}}
Expand Down
73 changes: 73 additions & 0 deletions docs/src/testing/the-setup-contract-test-macro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# The setup_contract_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 test only the contract methods and we want to deploy the contract with the default configuration parameters. The `setup_contract_test!` macro can do exactly that.

---

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

`COMMAND` is either `Wallets`, `Abigen` or `Deploy`.

`ARG` is either a:

* name-value (e.g. `name="MyContract"`), or,
* a literal (e.g. `"some_str_literal"`, `true`, `5`, ...)

Available `COMMAND`s:

Wallets
---

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

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

Cardinality: 0 or 1.

Abigen
---

Example: `Abigen(name="MyContract", abi="some_folder")`

Description: Generates the contract bindings under the name `name`. `abi`
should point to the folder containing the `out` directory of the forc build.

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.


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

```rust,ignore
{{#include ../../../examples/contracts/src/lib.rs:deploy_contract_setup_macro_short}}
```

> **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.
```rust,ignore
{{#include ../../../packages/fuels/tests/contracts.rs:contract_setup_macro_multi}}
```

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.

```rust,ignore
{{#include ../../../packages/fuels/tests/contracts.rs:contract_setup_macro_manual_wallet}}
```
15 changes: 15 additions & 0 deletions examples/abigen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "fuels-example-abigen"
publish = false
version = "0.0.0"
authors = ["Fuel Labs <contact@fuel.sh>"]
edition = "2021"
homepage = "https://fuel.network/"
license = "Apache-2.0"
repository = "https://github.com/FuelLabs/fuels-rs"
description = "Fuel Rust SDK abigen examples."

[dev-dependencies]
fuels = { version = "0.33.0", path = "../../packages/fuels" }
tokio = { version = "1.10", features = ["full"] }
anyhow = "1.0.68"
19 changes: 19 additions & 0 deletions examples/abigen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#[cfg(test)]
mod tests {
use anyhow::Result;
use fuels::prelude::*;

#[tokio::test]
async fn example_of_abigen_usage() -> Result<()> {
// ANCHOR: multiple_abigen_program_types
abigen!(
Contract(name="ContractA", abi="packages/fuels/tests/bindings/sharing_types/contract_a/out/debug/contract_a-abi.json"),
Contract(name="ContractB", abi="packages/fuels/tests/bindings/sharing_types/contract_b/out/debug/contract_b-abi.json"),
Script(name="MyScript", abi="packages/fuels/tests/scripts/script_with_arguments/out/debug/script_with_arguments-abi.json"),
Predicate(name="MyPredicate", abi="packages/fuels/tests/predicates/predicate_basic/out/debug/predicate_basic-abi.json"),
);
// ANCHOR_END: multiple_abigen_program_types

Ok(())
}
}
Loading

0 comments on commit a8c4586

Please sign in to comment.