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-rsis still in active development.
Documentation
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:
forc build --release --path e2eThen you can run the SDK tests with:
cargo testYou 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:
cargo test --test types in_vector -- --show-outputHow to run WASM tests?
You need to have wasm32 as a target, if you don't already:
rustup target add wasm32-unknown-unknownYou also need wasm-pack, if you don't already:
cargo install wasm-packNavigate 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:
cargo clean
rm Cargo.lock
forc build --release --path e2e
cargo testWhy 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:
cargo install mdbookNext, navigate to the docs folder and run the command below to start a local server and open a new tab in your browser.
mdbook serve --openYou can build the book by running:
mdbook buildFile: docs/src/abigen/index.md
Generating bindings with abigen
You might have noticed this snippet in the previous sections:
{{#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:
ProgramType(name="MyProgramType", abi="my_program-abi.json")...where:
ProgramTypeis one of:Contract,ScriptorPredicate,nameis the name that will be given to the generated bindings,abiis 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:
{{#include ../../../examples/macros/src/lib.rs:multiple_abigen_program_types}}How does the generated code look?
A rough overview:
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 oneabigen!call. Doing it in this manner will allow type sharing and avoid name collisions you'd normally get when callingabigen!multiple times inside the same namespace. If you choose to proceed otherwise, keep in mind the generated code overview presented above and appropriately separate theabigen!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:
{{#include ../../../examples/rust_bindings/src/abi.json}}By doing this:
{{#include ../../../examples/rust_bindings/src/lib.rs:use_abigen}}or this:
{{#include ../../../examples/rust_bindings/src/lib.rs:abigen_with_string}}you'll generate this (shortened for brevity's sake):
{{#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:
{{#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:
contract;
abi MyContract {
fn test_function() -> bool;
}
impl MyContract for Contract {
fn test_function() -> bool {
true
}
}The JSON ABI file looks like this:
$ 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:
- Amount
- Asset ID
- 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.
{{#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:
{{#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:
{{#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:
{{#include ../../../packages/fuels-core/src/utils/constants.rs:default_call_parameters}}This way:
{{#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.
{{#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:
- You have to choose between
.call()and.simulate()(more on this in the next section). - Contract calls are asynchronous, so you can choose to either
.awaitit or perform concurrent tasks, making full use of Rust's async. .unwrap()theResult<CallResponse, Error>returned by the contract call.
Once you unwrap the CallResponse, you have access to this struct:
{{#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).
receiptswill hold all receipts generated by that specific contract call.gas_usedis the amount of gas consumed by the contract call.tx_idwill 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.
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.
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.
{{#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:
{{#include ../../../packages/fuels-accounts/src/provider.rs:transaction_cost}}Note
script_gasrefers 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.
{{#include ../../../examples/contracts/src/lib.rs:contract_call_cost_estimation}}{{#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:
{{#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.
{{#include ../../../examples/contracts/src/lib.rs:add_custom_inputs_outputs}}Note: if custom inputs include coins that need to be signed, use the
add_signermethod to add the appropriate signer.