fuels-rs Documentation

Official Rust developer resources and documentation for FuelLabs/fuels-rs with 43k stars. Verified technical context for AI.

43,137 stars Rust Rust 1 file ยท ~3,871 tokens
RAW Doc

File: README.md

fuels-rs




Rust SDK for Fuel. It can be used for a variety of things, including but not limited to:

  • Compiling, deploying, and testing Sway contracts;
  • Launching a local Fuel network;
  • Crafting and signing transactions with hand-crafted scripts or contract calls;
  • Generating type-safe Rust bindings of contract methods;
  • And more, fuels-rs is still in active development.

Documentation

See the fuels-rs book

Features

  • Launch Fuel nodes
  • Deploy contracts
  • Interact with deployed contracts
  • Type-safe Sway contracts bindings code generation
  • Run Sway scripts
  • CLI for common operations
  • Local test wallets
  • Wallet integration
  • Events querying/monitoring

FAQ

What dependencies do I need?

How can I run the SDK tests?

First, build the test projects using forc:

shell
forc build --release --path e2e

Then you can run the SDK tests with:

shell
cargo test

You can also run specific tests. The following example will run all integration tests in types.rs whose names contain in_vector and show their outputs:

shell
cargo test --test types in_vector -- --show-output

How to run WASM tests?

You need to have wasm32 as a target, if you don't already:

shell
rustup target add wasm32-unknown-unknown

You also need wasm-pack, if you don't already:

shell
cargo install wasm-pack

Navigate to packages/wasm-tests and run wasm-pack test.

What to do if my tests are failing on `master`

Before doing anything else, try all these commands:

shell
cargo clean
rm Cargo.lock
forc build --release --path e2e
cargo test

Why is the prefix `fuels` and not `fuel`?

In order to make the SDK for Fuel feel familiar with those coming from the ethers.js ecosystem, this project opted for an s at the end. The fuels-* family of SDKs is inspired by The Ethers Project.

How can I run the docs locally?

Install mdbook by running:

shell
cargo install mdbook

Next, navigate to the docs folder and run the command below to start a local server and open a new tab in your browser.

shell
mdbook serve --open

You can build the book by running:

shell
mdbook build

File: docs/src/abigen/index.md

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), 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.


File: docs/src/abigen/the-abigen-macro.md

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/macros/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 program bindings that use it. Reexports are added to each mod so that even if a type is deemed shared, you can still access it as though each mod had generated the type for itself (i.e. my_contract_mod::SharedType).

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: docs/src/abigen/the-json-abi-file.md

The JSON ABI file

Whether you want to deploy or connect to a pre-existing smart contract, the JSON ABI file is extremely important: it's what tells the SDK about the ABI methods in your smart contracts.

For the same example Sway code as above:

rust
contract;

abi MyContract {
    fn test_function() -> bool;
}

impl MyContract for Contract {
    fn test_function() -> bool {
        true
    }
}

The JSON ABI file looks like this:

json
$ cat out/release/my-test-abi.json
[
  {
    "type": "function",
    "inputs": [],
    "name": "test_function",
    "outputs": [
      {
        "name": "",
        "type": "bool",
        "components": null
      }
    ]
  }
]

The Fuel Rust SDK will take this file as input and generate equivalent methods (and custom types if applicable) that you can call from your Rust code.


File: docs/src/calling-contracts/call-params.md

Call parameters

The parameters for a contract call are:

  1. Amount
  2. Asset ID
  3. Gas forwarded

You can use these to forward coins to a contract. You can configure these parameters by creating an instance of CallParameters and passing it to a chain method called call_params.

For instance, suppose the following contract that uses Sway's msg_amount() to return the amount sent in that transaction.

rust,ignore
{{#include ../../../e2e/sway/contracts/contract_test/src/main.sw:msg_amount}}

Then, in Rust, after setting up and deploying the above contract, you can configure the amount being sent in the transaction like this:

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

call_params returns a result to ensure you don't forward assets to a contract method that isn't payable.

In the following example, we try to forward an amount of 100 of the base asset to non_payable. As its name suggests, non_payable isn't annotated with #[payable] in the contract code. Passing CallParameters with an amount other than 0 leads to an error:

rust,ignore
{{#include ../../../e2e/tests/contracts.rs:non_payable_params}}

Note: forwarding gas to a contract call is always possible, regardless of the contract method being non-payable.

You can also use CallParameters::default() to use the default values:

rust,ignore
{{#include ../../../packages/fuels-core/src/utils/constants.rs:default_call_parameters}}

This way:

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

The gas_forwarded parameter defines the limit for the actual contract call as opposed to the gas limit for the whole transaction. This means that it is constrained by the transaction limit. If it is set to an amount greater than the available gas, all available gas will be forwarded.

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

If you don't set the call parameters or use CallParameters::default(), the transaction gas limit will be forwarded instead.


File: docs/src/calling-contracts/call-response.md

Call response

You've probably noticed that you're often chaining .call().await.unwrap(). That's because:

  1. You have to choose between .call() and .simulate() (more on this in the next section).
  2. Contract calls are asynchronous, so you can choose to either .await it or perform concurrent tasks, making full use of Rust's async.
  3. .unwrap() the Result<CallResponse, Error> returned by the contract call.

Once you unwrap the CallResponse, you have access to this struct:

rust,ignore
{{#include ../../../packages/fuels-programs/src/responses/call.rs:call_response}}

Where value will hold the value returned by its respective contract method, represented by the exact type returned by the FuelVM, E.g., if your contract returns a FuelVM's u64, value's D will be a u64. If it's a FuelVM's tuple (u8,bool), then D will be a (u8,bool). If it's a custom type, for instance, a Sway struct MyStruct containing two components, a u64, and a b256, D will be a struct generated at compile-time, called MyStruct with u64 and a [u8; 32] (the equivalent of b256 in Rust).

  • receipts will hold all receipts generated by that specific contract call.
  • gas_used is the amount of gas consumed by the contract call.
  • tx_id will hold the ID of the corresponding submitted transaction.

Error handling

You can use the is_ok and is_err methods to check if a contract call Result is Ok or contains an error. These methods will return either true or false.

rust, ignore
let is_ok = response.is_ok();
let is_error = response.is_err();

If is_err returns true, you can use the unwrap_err method to unwrap the error message.

rust, ignore
if response.is_err() {
    let err = response.unwrap_err();
    println!("ERROR: {:?}", err);
};

File: docs/src/calling-contracts/calls-with-different-wallets.md

Calls with different wallets

You can use the with_account() method on an existing contract instance as a shorthand for creating a new instance connected to the provided wallet. This lets you make contracts calls with different wallets in a chain like fashion.

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

Note: connecting a different wallet to an existing instance ignores its set provider in favor of the provider used to deploy the contract. If you have two wallets connected to separate providers (each communicating with a separate fuel-core), the one assigned to the deploying wallet will also be used for contract calls. This behavior is only relevant if multiple providers (i.e. fuel-core instances) are present and can otherwise be ignored.


File: docs/src/calling-contracts/cost-estimation.md

Estimating contract call cost

With the function estimate_transaction_cost(tolerance: Option<f64>, block_horizon: Option) provided by CallHandler, you can get a cost estimation for a specific call. The return type, TransactionCost, is a struct that contains relevant information for the estimation:

rust,ignore
{{#include ../../../packages/fuels-accounts/src/provider.rs:transaction_cost}}

Note script_gas refers to the part of the gas spent on the script execution.

Below are examples that show how to get the estimated transaction cost from single and multi call transactions.

rust,ignore
{{#include ../../../examples/contracts/src/lib.rs:contract_call_cost_estimation}}
rust,ignore
{{#include ../../../examples/contracts/src/lib.rs:multi_call_cost_estimation}}

The transaction cost estimation can be used to set the gas limit for an actual call, or to show the user the estimated cost.

Note The same estimation interface is available for scripts.


File: docs/src/calling-contracts/custom-asset-transfer.md

Custom asset transfer

The SDK provides the option to transfer assets within the same transaction, when making a contract call. By using add_custom_asset() you specify the asset ID, the amount, and the destination address:

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

File: docs/src/calling-contracts/custom-inputs-outputs.md

Custom inputs and outputs

If you need to add specific inputs and outputs to contract calls, you can use the with_inputs and with_outputs methods.

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

Note: if custom inputs include coins that need to be signed, use the add_signer method to add the appropriate signer.