The Sway Programming Language

Sway is a domain-specific language (DSL) for the Fuel Virtual Machine (FuelVM), a blockchain-optimized VM designed for the Fuel blockchain. Sway is based on Rust, and includes syntax to leverage a blockchain VM without needlessly verbose boilerplate.

This book documents how to write smart contracts in Sway, along with how to install and use the Sway toolchain.

Before starting developing smart contracts in Sway, please keep in mind the temporary workarounds and missing features of the language and toolchain.

Introduction

To get started with Forc and Sway smart contract development, install the Sway toolchain and Fuel full node and set up your first project.

Installation

Note that if you want to run (e.g. for testing) Sway smart contracts, a Fuel Core full node is required. Otherwise, the Sway toolchain is sufficient to compile Sway smart contracts.

Dependencies

A prerequisite for installing and using Sway is the Rust toolchain. Platform-specific instructions can be found here.

Installing fuel-core may require installing additional system dependencies. See here for instructions.

Installing from Cargo

The Sway toolchain and Fuel Core full node can be installed with:

cargo install forc fuel-core

forc and fuel-core are built and tested against the stable Rust toolchain version 1.58 or later. If your install fails the first time, use rustup update and try again.

There is no guarantee that either package will work with the nightly Rust toolchain, so ensure you are using stable with:

rustup default stable

Updating forc

You can update forc and fuel-core with:

cargo install forc fuel-core

Installing forc Plugins

The Fuel ecosystem has a few plugins which can be easily installed via cargo.

Note, forc detects anything in your path prefixed with forc- as a plugin. Use forc plugins to see what you currently have installed.

# Sway Formatter
cargo install forc-fmt

# Block Explorer
cargo install forc-explore

# Forc Language Server
cargo install forc-lsp

Building from Source

The Sway toolchain can be built from source by following instructions at https://github.com/FuelLabs/sway.

The Fuel Core full node implementation can be built from source by following instructions at https://github.com/FuelLabs/fuel-core.

Enable tab completion for Bash, Fish, Zsh, or PowerShell

forc now supports generating completion scripts for Bash, Fish, Zsh, and PowerShell. See forc completions --help for full details, but the gist is as simple as using one of the following:

# Bash
$ forc completions --shell=bash > ~/.local/share/bash-completion/completions/forc

# Bash (macOS/Homebrew)
$ forc completions --shell=bash > $(brew --prefix)/etc/bash_completion.d/forc.bash-completion

# Fish
$ mkdir -p ~/.config/fish/completions
$ forc completions --shell=fish > ~/.config/fish/completions/forc.fish

# Zsh
$ forc completions --shell=zsh > ~/.zfunc/_forc

# PowerShell v5.0+
$ forc completions --shell=powershell >> $PROFILE.CurrentUserCurrentHost
# or
$ forc completions --shell=powershell | Out-String | Invoke-Expression

Once the completions have been generated and properly installed, close and reopen your terminal for the new completions to take effect.

Getting Started

Follow this guide to write and deploy a simple wallet smart contract in Sway.

Glossary

Before we begin, it may be helpful to understand terminology that will used throughout the docs and how they relate to each other:

  • Fuel: the Fuel blockchain.
  • FuelVM: the virtual machine powering Fuel.
  • Sway: the domain-specific language crafted for the FuelVM; it is inspired by Rust.
  • Forc: the build system and package manager for Sway, similar to Cargo for Rust.

Understand Sway Program Types

There are four types of Sway programs:

  • contract
  • predicate
  • script
  • library

Contracts, predicates, and scripts can produce artifacts usable on the blockchain, while a library is simply a project designed for code reuse and is not directly deployable.

Every Sway file must begin with a declaration of what type of program it is.

See the chapter on program types for more information.

Create Wallet Projects with forc

To deploy a wallet on Fuel, we will need to write a library, a contract, and a script in Sway.

First, let's install the Sway toolchain. Then with forc installed, let's create three different sibling projects:

forc init wallet_lib
forc init wallet_contract
forc init wallet_script

See here for more information on Forc project structure.

Write a Sway Smart Contract

Declare ABI in wallet_lib

Navigate into the src/main.sw file of the wallet_lib directory you just created.

Delete the auto-generated skeleton code currently in the file, and copy and paste the following code:

library wallet_lib;

abi Wallet {
    fn receive_funds();
    fn send_funds(amount_to_send: u64, recipient_address: b256);
}

Every Sway file must start with a declaration of what type of program the file contains; here, we've declared that this file is a library called wallet_lib.

Sway contracts should declare an ABI—an application binary interface—in a library so that it can be re-used by downstream contracts. Let's focus on the ABI declaration and inspect it line-by-line.

In the first line, we declare the name of this ABI: Wallet. To import this ABI into either a script or another contract for calling the contract, or the contract to implement the ABI, you would use use wallet_lib::Wallet;.

In the second line we declare an ABI method called receive_funds which, when called, should receive funds into this wallet. This method takes no parameters and does not return anything.

Note that we are simply defining an interface here, so there is no function body or implementation of the function. We only need to define the interface itself. In this way, ABI declarations are similar to trait declarations.

In the third line we declare another ABI method, this time called send_funds. It takes two parameters: the amount to send, and the address to send the funds to.

Implementing the ABI Methods in wallet_contract

Now that we've defined the interface, let's discuss how to use it. We will start by implementing the above ABI for a specific contract.

To do this, navigate to the wallet_contract directory that you created with forc previously.

First, you need to import the Wallet declaration from the last step. Open up Forc.toml. It should look something like this:

[project]
authors = ["user"]
entry = "main.sw"
license = "Apache-2.0"
name = "wallet_contract"

[dependencies]

Include the wallet_lib project as a dependency by adding the following line to the bottom of the file:

wallet_lib = { path = "../wallet_lib" }

Now, open up main.sw in wallet_contract/src and copy and paste the following code:

contract;
use wallet_lib::Wallet;

impl Wallet for Contract {
    fn receive_funds() {
    }

    fn send_funds(amount_to_send: u64, recipient_address: b256) {
    }
}

This implements the ABI methods with empty bodies. Actual implementation of the bodies is left as an exercise for the reader.

Build the Contract

Build wallet_contract by running

forc build

from inside the wallet_contract directory.

Deploy the Contract

It's now time to deploy the wallet contract and call it on a Fuel node. We will show how to do this using forc from the command line, but you can also do it using the Rust SDK or the TypeScript SDK

Spin Up a Fuel node

In a separate tab in your terminal, spin up a local Fuel node:

fuel-core --db-type in-memory

This starts a Fuel node with a volatile database that will be cleared when shut down (good for testing purposes).

Deploy wallet_contract To Your Local Fuel Node

To deploy wallet_contract on your local Fuel node, run

forc deploy

from the root of the wallet_contract directory.

This should produce some output in stdout that looks like this:

$ forc deploy
  Compiled library "wallet_lib".
  Compiled contract "wallet_contract".
  Bytecode size is 212 bytes.
Contract id: 0xf4b63e0e09cb72762cec18a6123a9fb5bd501b87141fac5835d80f5162505c38
Logs:
HexString256(HexFormatted(0xd9240bc439834bc6afc3f334abf285b3b733560b63d7ce1eb53afa8981984af7))

Note the contract ID—you will need it in the next step.

Write a Sway Script to Call a Sway Contract

Note that if you are using the SDK you do not need to write a script to call the Sway contract, this is all handled automagically by the SDK.

Now that we have deployed our wallet contract, we need to actually call our contract. We can do this by calling the contract from a script.

Let's navigate to the wallet_script directory created previously.

First, you need to import the wallet_lib library. Open up the Forc.toml in the root of the directory. Import wallet_lib repo by adding the following line to the bottom of the file:

wallet_lib = { path = "../wallet_lib" }

Next, open up src/main.sw. Copy and paste the following code:

script;

use std::constants::NATIVE_ASSET_ID;

use wallet_lib::Wallet;

fn main() {
    let caller = abi(Wallet, <contract_address>);
    caller.send_funds(200, 0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b);
}

Replace <contract_address> with the contract ID you noted when deploying the contract.

The main new concept is the abi cast: abi(AbiName, ContractAddress). This returns a ContractCaller type which can be used to call contracts. The methods of the ABI become the methods available on this contract caller: send_funds and receive_funds. We can directly call a contract ABI method as if it were a trait method.

Check That wallet_script Builds

To check that wallet_script builds successfully, run

forc build

from the root of the wallet_script directory.

Call the Contract

It's now time to call the contract. We will show how to do this using forc from the command line, but you can also do this using the Rust SDK or the TypeScript SDK

Run wallet_script Against Your Local Fuel Node

To run the script now against the local Fuel node, run

forc run --contract <contract-id>

from the root of the wallet_script directory.

Note that we are passing in the wallet_contract contract ID as a command-line parameter. You will need to pass in the contract ID of every contract that this script will be interacting with.

If the script is successfully run, it will output something that looks like:

$ forc run --contract <contract-id>
  Compiled library "lib-std".
  Compiled library "wallet_lib".
  Compiled script "wallet_script".
  Bytecode size is 272 bytes.
[Call { id: 0xf4b63e0e09cb72762cec18a6123a9fb5bd501b87141fac5835d80f5162505c38, to: 0xf4b63e0e09cb72762cec18a6123a9fb5bd501b87141fac5835d80f5162505c38, amount: 0, color: 0x0000000000000000000000000000000000000000000000000000000000000000, gas: 10000, a: 1506869579, b: 968, pc: 1656, is: 1656 }, Return { id: 0xf4b63e0e09cb72762cec18a6123a9fb5bd501b87141fac5835d80f5162505c38, val: 0, pc: 1764, is: 1656 }, Return { id: 0x0000000000000000000000000000000000000000000000000000000000000000, val: 0, pc: 584, is: 472 }, ScriptResult { result: InstructionResult { reason: RESERV00, instruction: Instruction { op: 0, ra: 0, rb: 0, rc: 0, rd: 0, imm06: 0, imm12: 0, imm18: 0, imm24: 0 } }, gas_used: 998 }]

It returns a Call receipt and a ScriptResult receipt.

Testing Sway contracts

The recommended way to test Sway contracts is via the Rust SDK. You may also write tests in TypeScript if you are using the TypeScript SDK.

The Sway Toolchain

The Sway toolchain consists of several components.

Forc (forc)

The "Fuel Orchestrator" Forc is our equivalent of Rust's Cargo. It is the primary entry point for creating, building, testing, and deploying Sway projects.

Sway Language Server (forc-lsp)

The Sway Language Server forc-lsp is provided to expose features to IDEs, which you can install with cargo:

cargo install forc-lsp

Currently, only Visual Studio Code is supported through a plugin. Vim support is forthcoming, though syntax highlighting is provided.

Note that there is no need to manually run forc lsp (the plugin will automatically start it), however forc must be in your $PATH. To check if forc is in your $PATH, type forc --help in your terminal.

Fuel Core (fuel-core)

While not directly part of the Sway toolchain, an implementation of the Fuel protocol, Fuel Core, is provided. Note that the SDK will automatically start and stop an instance of the node during tests, so there is no need to manually run a node unless using Forc directly without the SDK.

A Forc Project

To initialize a new project with Forc, use forc init:

forc init my-fuel-project

Here is the project that Forc has initialized:

$ cd my-fuel-project
$ tree .
├── Cargo.toml
├── Forc.toml
├── src
│   └── main.sw
└── tests
    └── harness.rs

Forc.toml is the manifest file (similar to Cargo.toml for Cargo or package.json for Node), and defines project metadata such as the project name and dependencies.

For additional information on dependency management, see: here.

[project]
authors = ["User"]
entry = "main.sw"
license = "Apache-2.0"
name = "my-fuel-project"

[dependencies]

Here are the contents of the only Sway file in the project, and the main entry point, src/main.sw:

contract;

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

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

The project is a contract, one of four different project types. For additional information on different project types, see here.

We now compile our project with forc build, passing the flag --print-finalized-asm to view the generated assembly:

$ forc build --print-finalized-asm
.program:
ji   i4
noop
DATA_SECTION_OFFSET[0..32]
DATA_SECTION_OFFSET[32..64]
lw   $ds $is 1
add  $$ds $$ds $is
lw   $r1 $fp i73              ; load input function selector
lw   $r0 data_1               ; load fn selector for comparison
eq   $r0 $r1 $r0              ; function selector comparison
jnzi $r0 i11                  ; jump to selected function
rvrt $zero                    ; revert if no selectors matched
lw   $r0 data_0               ; literal instantiation
ret  $r0
.data:
data_0 .bool 0x01
data_1 .u32 0x2151bd4b

  Compiled contract "my-fuel-project".
  Bytecode size is 68 bytes.

To test this contract, use forc test:

$ forc test
running 1 test
test can_get_contract_id ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s

The forc test command tests the contract using the Rust SDK test harness that lives under tests/. The default test harness harness.rs contains boilerplate code to get you started but doesn't actually call any contract methods. For additional information on testing contracts using the Rust SDK, refer to the Testing with Rust section.

Standard Library

Similar to Rust, Sway comes with its own standard library.

The Sway Standard Library is the foundation of portable Sway software, a set of minimal shared abstractions for the broader Sway ecosystem. It offers core types, like Result<T, E> and Option<T>, library-defined operations on language primitives, native asset management, blockchain contextual operations, access control, storage management, and support for types from other VMs, among many other things.

The standard library is made implicitly available to all Forc projects created using forc init. Importing items from the standard library can be done using the use keyword. Example:

use std::address::Address;

Using the Standard Library

Aside from the references to the standard library accross this book, you may also read the std library project directly here.

Example

Some basic example contracts to see how Sway and Forc work.

Counter

The following is a simple example of a contract which implements a counter. Both the initialize() and increment() functions return the currently set value.

forc init --template counter my_counter_project

The use of storage here is new and is still being stabilized, please see the Subcurrency example for writing storage manually.

contract;

abi TestContract {
    fn initialize_counter(value: u64) -> u64;
    fn increment_counter(amount: u64) -> u64;
}

storage {
    counter: u64,
}

impl TestContract for Contract {
    fn initialize_counter(value: u64) -> u64 {
        storage.counter = value;
        value
    }

    fn increment_counter(amount: u64) -> u64 {
        let incremented = storage.counter + amount;
        storage.counter = incremented;
        incremented
    }
}

Subcurrency

The following is a simple example of a subcurrency which implements a mint and send function for a token. This example does not use Fuel's native asset system (see: Native Assets).

contract;

use std::{
    address::Address,
    assert::assert,
    chain::auth::{AuthError, Sender, msg_sender},
    hash::sha256,
    result::*,
    revert::revert,
    storage::{get, store}
};

////////////////////////////////////////
// Event declarations
////////////////////////////////////////

// Events allow clients to react to changes in the contract.
// Unlike Solidity, events are simply structs.
// Note: Serialization is not yet implemented, therefore logging
//  of arbitrary structures will not work without manual
//  serialization.

/// Emitted when a token is sent.
struct Sent {
    from: Address,
    to: Address,
    amount: u64,
}

////////////////////////////////////////
// ABI declarations
////////////////////////////////////////

/// ABI definition for a subcurrency.
abi Token {
    // Mint new tokens and send to an address.
    // Can only be called by the contract creator.
    fn mint(receiver: Address, amount: u64);

    // Sends an amount of an existing token.
    // Can be called from any address.
    fn send(receiver: Address, amount: u64);
}

////////////////////////////////////////
// Constants
////////////////////////////////////////

/// Address of contract creator.
const MINTER: b256 = 0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b;

////////////////////////////////////////
// Contract storage
////////////////////////////////////////

// Contract storage persists across transactions.
// Note: Contract storage mappings are not implemented yet.
const STORAGE_BALANCES: b256 = 0x0000000000000000000000000000000000000000000000000000000000000000;

////////////////////////////////////////
// ABI definitions
////////////////////////////////////////

/// Contract implements the `Token` ABI.
impl Token for Contract {
    fn mint(receiver: Address, amount: u64) {
        let sender: Result<Sender, AuthError> = msg_sender();
        let sender = if let Sender::Address(addr) = sender.unwrap() {
            assert(addr.into() == MINTER);
        } else {
            revert(0);
        };

        // Increase the balance of receiver
        let storage_slot = sha256((STORAGE_BALANCES, receiver.into()));
        let mut receiver_amount = get::<u64>(storage_slot);
        store(storage_slot, receiver_amount + amount);
    }

    fn send(receiver: Address, amount: u64) {
        let sender: Result<Sender, AuthError> = msg_sender();
        let sender = if let Sender::Address(addr) = sender.unwrap() {
            addr
        } else {
            revert(0);
        };

        // Reduce the balance of sender
        let sender_storage_slot = sha256((STORAGE_BALANCES, sender.into()));
        let mut sender_amount = get::<u64>(sender_storage_slot);
        assert(sender_amount > amount);
        store(sender_storage_slot, sender_amount - amount);

        // Increase the balance of receiver
        let receiver_storage_slot = sha256((STORAGE_BALANCES, receiver.into()));
        let mut receiver_amount = get::<u64>(receiver_storage_slot);
        store(receiver_storage_slot, receiver_amount + amount);
    }
}

FizzBuzz

This example is not the traditional fizzbuzz, instead it is the smart contract version! A script can call this contract with some u64 value and receive back its fizzbuzzability as an enum. Note that the deserialization scheme for the fizzbuzz enum will be included in the ABI descriptor so the caller knows what to do with the bytes.

contract;

enum FizzBuzzResult {
    Fizz: (),
    Buzz: (),
    FizzBuzz: (),
    Other: u64,
}

abi FizzBuzz {
    fn fizzbuzz(input: u64) -> FizzBuzzResult;
}

impl FizzBuzz for Contract {
    fn fizzbuzz(input: u64) -> FizzBuzzResult {
        if input % 15 == 0 {
            FizzBuzzResult::FizzBuzz
        } else if input % 3 == 0 {
            FizzBuzzResult::Fizz
        } else if input % 5 == 0 {
            FizzBuzzResult::Buzz
        } else {
            FizzBuzzResult::Other(input)
        }
    }
}

Wallet Smart Contract

Contract storage in the language syntax is a work-in-progress feature, and the following example does not currently compile.

contract;

use std::{
    address::Address,
    assert::assert,
    chain::auth::{AuthError, Sender, msg_sender},
    constants::NATIVE_ASSET_ID,
    context::{call_frames::msg_asset_id, msg_amount},
    contract_id::ContractId,
    result::*,
    revert::revert,
    token::transfer_to_output,
};

const OWNER_ADDRESS: b256 = 0x8900c5bec4ca97d4febf9ceb4754a60d782abbf3cd815836c1872116f203f861;

storage {
    balance: u64,
}

abi Wallet {
    fn receive_funds();
    fn send_funds(amount_to_send: u64, recipient_address: Address);
}

impl Wallet for Contract {
    fn receive_funds() {
        if msg_asset_id().into() == NATIVE_ASSET_ID {
            storage.balance = storage.balance + msg_amount();
        }
    }

    fn send_funds(amount_to_send: u64, recipient_address: Address) {
        let sender: Result<Sender, AuthError> = msg_sender();
        if let Sender::Address(addr) = sender.unwrap() {
            assert(addr.into() == OWNER_ADDRESS);
        } else {
            revert(0);
        }

        let current_balance = storage.balance;
        assert(current_balance > amount_to_send);
        storage.balance = current_balance - amount_to_send;
        transfer_to_output(amount_to_send, ~ContractId::from(NATIVE_ASSET_ID), recipient_address);
    }
}

Sway Program Types

A Sway program itself has a type: it is either a contract, a predicate, a script, or a library. The first three of these things are all deployable to the blockchain. A library is simply a project designed for code reuse and is never directly deployed to the chain.

Every Sway file must begin with a declaration of what type of program it is. A project can have many libraries within it, but only one contract, script, or predicate. Scripts and predicates require main functions to serve as entry points, while contracts instead publish an ABI. This chapter will go into detail about all of these various types of programs and what purposes they serve.

Contracts are used primarily for protocols or systems that operate within a fixed set of rules. A good example would be a staking contract or a decentralized exchange.

Scripts are used for complex on-chain interactions that won't persist. An example of this may be using a DEX and Lender to create a leveraged position (borrow, swap, re-collateralize, borrow) which is a complex transaction that would usually take multiple steps.

Libraries are for code that is reusable and useful for handling common situations. A good example of this would be a library to handle fixed-point math or big number math.

What is a Smart Contract?

A smart contract is no different than a script or predicate in that it is a piece of bytecode that is deployed to the blockchain via a transaction. The main features of a smart contract that differentiate it from scripts or predicates are that it is callable and stateful. Put another way, a smart contract is analogous to a deployed API with some database state. The interface of a smart contract, also just called a contract, must be defined strictly with an ABI declaration. See this contract for an example.

Syntax of a Smart Contract

As with any Sway program, the program starts with a declaration of what program type it is. A contract must also either define or import an ABI declaration and implement it. It is considered good practice to define your ABI in a separate library and import it into your contract. This allows callers of your contract to simply import the ABI directly and use it in their scripts to call your contract. Let's take a look at an ABI declaration in a library:

library wallet_abi;

abi Wallet {
    fn receive_funds();
    fn send_funds(amount_to_send: u64, recipient_address: b256);
}

Let's focus on the ABI declaration and inspect it line-by-line.

The ABI Declaration

abi Wallet {
    fn receive_funds();
    fn send_funds(amount_to_send: u64, recipient_address: b256);
}

In the first line, abi Wallet {, we declare the name of this Application Binary Interface, or ABI. We are naming this ABI Wallet. To import this ABI into either a script for calling or a contract for implementing, you would use use wallet_abi::Wallet;.


In the second line,

    fn receive_funds();

we are declaring an ABI method called receive_funds which, when called, should receive funds into this wallet. Note that we are simply defining an interface here, so there is no function body or implementation of the function. We only need to define the interface itself. In this way, ABI declarations are similar to trait declarations. This particular ABI method does not take any parameters.


In the third line,

    fn send_funds(amount_to_send: u64, recipient_address: Address);

we are declaring another ABI method, this time called send_funds. It takes two parameters: the amount to send, and the address to send the funds to.

Implementing an ABI for a Smart Contract

Now that we've discussed how to define the interface, let's discuss how to use it. We will start by implementing the above ABI for a specific contract.

Implementing an ABI for a contract is accomplished with impl <ABI name> for Contract syntax. The for Contract syntax can only be used to implement an ABI for a contract; implementing methods for a struct should use impl Foo syntax.

impl Wallet for Contract {
    fn receive_funds() {
        if asset_id == NATIVE_ASSET_ID {
            let balance = storage.balance.write();
            deref balance = balance + coins_to_forward;
        };
    }

    fn send_funds(amount_to_send: u64, recipient_address: b256) {
        assert(sender() == OWNER_ADDRESS);
        assert(storage.balance.read() > amount_to_send);
        let balance = storage.balance.write();
        deref balance = balance - amount_to_send;
        transfer_coins(asset_id, recipient_address, amount_to_send);
    }
}

You may notice once again the similarities between traits and ABIs. And, indeed, as a bonus, you can specify methods in addition to the interface surface of an ABI, just like a trait. By implementing the methods in the interface surface, you get the extra method implementations For Free™.

Note that the above implementation of the ABI follows the Checks, Effects, Interactions pattern.

Calling a Smart Contract from a Script

Now that we have defined our interface and implemented it for our contract, we need to know how to actually call our contract. Let's take a look at a contract call:

script;

use std::consts::NATIVE_ASSET_ID;

use wallet_abi::Wallet;
use wallet_abi::SendFundsRequest;

fn main() {
    let contract_address = 0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b;
    let caller = abi(Wallet, contract_address);
    let amount_to_send = 200;
    let recipient_address: 0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b;
    caller.send_funds{gas: 10000, coins: 0, asset_id: NATIVE_ASSET_ID}(amount_to_send, recipient_address);
}

The main new concept is the abi cast: abi(AbiName, contract_address). This returns a ContractCaller type which can be used to call contracts. The methods of the ABI become the methods available on this contract caller: send_funds and receive_funds. We then directly call the contract ABI method as if it was just a regular method. You also have the option of specifying the following special parameters inside curly braces right before the main list of parameters:

  1. gas: represents the gas being forwarded to the contract when it is called.
  2. coins: represents how many coins are being forwarded with this call.
  3. asset_id: represents the ID of the asset type of the coins being forwarded.

Each special parameter is optional and assumes a default value when skipped.

Libraries

Libraries in Sway are files used to define new common behavior. An example of this is the Sway Core Library which outlines various methods that the u64 type implements.

Writing Libraries

Libraries are denoted using the library keyword at the beginning of the file, followed by a name so that they can be imported, e.g. library foo;.

library my_library;

A good reference library to use when learning library design is the Sway Core Library. The add function interface is defined via the Add trait and then implemented for u64. This attaches this add function to the type so that, when the trait is imported, u64s can utilize the add function.

pub trait Add {
    fn add(self, other: Self) -> Self;
}

impl Add for u64 {
    fn add(self, other: Self) -> Self {
        asm(r1: self, r2: other, r3) {
            add r3 r2 r1;
            r3: u64
        }
    }
}

This snippet defines the trait Add, then implements it for the u64 type by providing a function body. This gives all u64s the add function, which is inserted at compile time when you use the + operator in Sway. Libraries can export more than functions, though. You can also use libraries to just export types like below.

pub struct MyStruct {
    field_one: u64,
    field_two: bool,
}

Libraries are composed of just a Forc.toml file and a src directory, unlike contracts which usually contain a tests directory and a Cargo.toml file as well. An example of a library's Forc.toml:

[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "lib.sw"
license = "Apache-2.0"
name = "lib-std"

[dependencies]

which denotes the authors, an entry file, the name by which it can be imported, and any dependencies. For large libraries, it is recommended to have a lib.sw entry point re-export all other sub-libraries. For example, the lib.sw of the standard library looks like:

library std;

dep block;
dep storage;
dep constants;

with other libraries contained in the src folder, like the block library (inside of block.sw):

library block;

/// Get the current block height
pub fn height() -> u64 {
    asm(height) {
        bhei height;
        height: u64
    }
}

The dep keyword in the main library includes a dependency on another library, making all of its items (such as functions and structs) accessible from the main library. The dep keyword simply makes the library a dependency and fully accessible within the current context.

Using Libraries

Libraries can be imported using the use keyword and with a :: separating the name of the library and the import.

Here is an example of importing storage and its related functions from the standard library.

use std::storage::*;

Wildcard imports using * are supported, but it is always recommended to use explicit imports where possible.

You will also need to link the library in the Forc.toml of the forc repo that you're calling from. You can do this by opening up the Forc.toml file and adding the following line to the bottom:

wallet_lib = { path = "../wallet_lib" }

Scripts

A script is runnable bytecode on the chain which executes once to perform some task. It does not represent ownership of any resources and it cannot be called by a contract. A script can return a single value of any type.

Scripts are state-aware in that while they have no persistent storage (because they only exist during the transaction) they can call contracts and act based upon the returned values and results.

This example script calls a contract:

script;

use example_contract::MyContract;

struct InputStruct {
    field_1: bool,
    field_2: u64,
}

// All scripts require a main function.
fn main () {
    let x = abi(MyContract, 0x8900c5bec4ca97d4febf9ceb4754a60d782abbf3cd815836c1872116f203f861);
    let asset_id = 0x7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777;
    let input = InputStruct {
        field_1: true,
        field_2: 3,
    };
    x.foo(5000, 0, asset_id, input);
}

Scripts, similar to predicates, rely on a main() function as an entry point. You can call other functions defined in a script from the main() function or call another contract via an abi cast.

An example use case for a script would be a router that trades funds through multiple DEXes to get the price for the input asset, or a script to re-adjust a Collateralized Debt Position via a flashloan.

Scripts and the SDKs

Unlike Ethereum transactions which can call a contract directly (but can only call a single contract), Fuel transactions execute a script, which may call zero or more contracts. The Rust and TypeScript SDKs provide functions to call contract methods as if they were calling contracts directly. Under the hood, the SDKs wrap all contract calls with scripts that contain minimal code to simply make the call and forward script data as call parameters.

Predicates

From the perspective of Sway, predicates are programs that return a Boolean value and which represent ownership of some resource upon execution to true. They have no access to contract storage. Here is a trivial predicate, which always evaluates to true:

predicate;

// All predicates require a main function which returns a Boolean value.
fn main() -> bool {
    true
}

Sway Language basics

Sway is a programming language designed for the FuelVM. It is a statically typed, compiled language with type inference and traits. Sway aims to make smart contract development safer and more performant through the use of strong static analysis and compiler feedback.

Sway basics.

Variables

Variables in Sway are immutable by default. This means that, by default, once a variable is declared, its value cannot change. This is one of the ways how Sway encourages safe programming, and many modern languages have this same default. Let's take a look at variables in detail.

Declaring a Variable

Let's look at a variable declaration:

let foo = 5;

Great! We have just declared a variable, foo. What do we know about foo?

  1. It is immutable.
  2. Its value is 5.
  3. Its type is u64, a 64-bit unsigned integer.

u64 is the default numeric type, and represents a 64-bit unsigned integer. See the section Built-in Types for more details.

We can also make a mutable variable. Let's take a look:

let mut foo = 5;
foo = 6;

Now, foo is mutable, and the reassignment to the number 6 is valid. That is, we are allowed to mutate the variable foo to change its value.

Type annotations

A variable declaration can contain a type annotation. A type annotation serves the purpose of declaring the type, in addition to the value, of a variable. Let's take a look:

let foo: u32 = 5;

We have just declared the type of the variable foo as a u32, which is an unsigned 32-bit integer. Let's take a look at a few other type annotations:

let bar: str[4] = "sway";
let baz: bool = true;

If the value declared cannot be assigned to the declared type, there will be an error generated by the compiler.

Built-in Types

Every value in Sway is of a certain type. Although deep down, all values are just ones and zeroes in the underlying virtual machine, Sway needs to know what those ones and zeroes actually mean. This is accomplished with types.

Sway is a statically typed language. At compile time, the types of every value must be known. This does not mean you need to specify every single type: usually, the type can be reasonably inferred by the compiler.

Primitive Types

Sway has the following primitive types:

  1. u8 (8-bit unsigned integer)
  2. u16 (16-bit unsigned integer)
  3. u32 (32-bit unsigned integer)
  4. u64 (64-bit unsigned integer)
  5. str[] (fixed-length string)
  6. bool (Boolean true or false)
  7. b256 (256 bits (32 bytes), i.e. a hash)

All other types in Sway are built up of these primitive types, or references to these primitive types. You may notice that there are no signed integers—this is by design. In the blockchain domain that Sway occupies, floating-point values and negative numbers have smaller utility, so their implementation has been left up to libraries for specific use cases.

Numeric Types

All of the unsigned integer types are numeric types, and the byte type can also be viewed as an 8-bit unsigned integer.

Numbers can be declared with binary syntax, hexadecimal syntax, base-10 syntax, and underscores for delineation. Let's take a look at the following valid numeric primitives:

0xffffff    // hexadecimal
0b10101010  // binary
10          // base-10
100_000     // underscore delineated base-10
0x1111_0000 // underscore delineated binary
0xfff_aaa   // underscore delineated hexadecimal

The default numeric type is u64. The FuelVM's word size is 64 bits, and the cases where using a smaller numeric type saves space are minimal.

Boolean Type

The boolean type (bool) has two potential values: true or false. Boolean values are typically used for conditional logic or validation, for example in if expressions. Booleans can be negated, or flipped, with the unary negation operator !. For example:

fn returns_false() -> bool {
    let boolean_value: bool = true;
    !boolean_value
}

String Type

In Sway, static-length strings are a primitive type. This means that when you declare a string, its size is a part of its type. This is necessary for the compiler to know how much memory to give for the storage of that data. The size of the string is denoted with square brackets. Let's take a look:

let my_string: str[4] = "fuel";

Because the string literal "fuel" is four letters, the type is str[4], denoting a static length of 4 characters. Strings default to UTF-8 in Sway.

Compound Types

Compound types are types that group multiple values into one type. In Sway, we have arrays and tuples.

Tuple Types

A tuple is a general-purpose static-length aggregation of types. In more plain terms, a tuple is a single type that consists of an aggregate of zero or more types. The internal types that make up a tuple, and the tuple's cardinality, define the tuple's type. Let's take a look at some examples.

let x: (u64, u64) = (0, 0);

This is a tuple, denoted by parenthesized, comma-separated values. Note that the type annotation, (u64, u64), is similar in syntax to the expression which instantiates that type, (0, 0).

let x: (u64, bool) = (42, true);
assert(x.1);

In this example, we have created a new tuple type, (u64, bool), which is a composite of a u64 and a bool. To access a value within a tuple, we use tuple indexing: x.1 stands for the first (zero-indexed, so the bool) value of the tuple. Likewise, x.0 would be the zeroth, u64 value of the tuple. Tuple values can also be accessed via destructuring:

struct Foo {}
let x: (u64, Foo, bool) = (42, Foo {}, true);
let (number, foo, boolean) = x;

Arrays

An array is similar to a tuple, but an array's values must all be of the same type. An array is written as a comma-separated list inside square brackets:

let x = [1, 2, 3, 4, 5];

Arrays are allocated on the stack since their size is known. An array's size is always static, i.e. it cannot change. An array of five elements cannot become an array of six elements.

Arrays can be iterated over, unlike tuples. A common use case for arrays is checking set membership. If you are given a name, and you'd like to figure out if that name is included in your list of classmates, you can use an array:

let name = /* some user input */;
let classmates = ["Bob", "Jan", "Ron"];
assert(classmates.contains(name));

An array's type is written as the type the array contains followed by the number of elements, semicolon-separated and within square brackets.

let x: [u64; 5] = [0, 1, 2, 3, 4];

To access an element in an array, use array indexing syntax:

let x: [bool; 2] = [true, false];
assert(x[0]);

Note that arrays are zero-indexed, just like tuples.

Blockchain Types

Sway is fundamentally a blockchain language, and it offers a selection of types tailored for the blockchain use case.

These are provided via the standard library (lib-std) which both add a degree of type-safety, as well as make the intention of the developer more clear.

Address Type

The Address type is a type-safe wrapper around the primitive b256 type. Unlike Ethereum, an address never refers to a deployed smart contract (see the ContractId type below). An Address can be either the hash of a public key (effectively an externally owned account if you're coming from Ethereum) or the hash of a predicate. Addresses own UTXOs.

An Address is implemented as follows.

pub struct Address {
    value: b256,
}

Casting between the b256 and Address types must be done explicitly:

let my_number: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
let my_address: Address = ~Address::from(my_number);
let forty_two: b256 = my_address.into();

ContractId Type

The ContractId type is a type-safe wrapper around the primitive b256 type. A contract's ID is a unique, deterministic identifier analogous to a contract's address on Ethereum. Contracts cannot own UTXOs but can own assets.

A ContractId is implemented as follows.

pub struct ContractId {
    value: b256,
}

Casting between the b256 and ContractId types must be done explicitly:

let my_number: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
let my_contract_id: ContractId = ~ContractId::from(my_number);
let forty_two: b256 = my_contract_id.into();

Functions

Functions in Sway are declared with the fn keyword. Let's take a look:

fn equals(first_param: u64, second_param: u64) -> bool {
    first_param == second_param
}

We have just declared a function named equals which takes two parameters: first_param and second_param. The parameters must both be 64-bit unsigned integers.

This function also returns a bool value, i.e. either true or false. This function returns true if the two given parameters are equal, and false if they are not. If we want to use this function, we can do so like this:

fn main() {
    equals(5, 5); // evaluates to `true`
    equals(5, 6); // evaluates to `false`
}

Structs and Tuples

Structs

Structs in Sway are a named grouping of types. You may also be familiar with structs via another name: product types. Sway does not make any significantly unique usages of structs; they are similar to most other languages which have structs. If you're coming from an object-oriented background, a struct is like the data attributes of an object.

To declare a struct type, use struct declaration syntax:

struct Foo {
    bar: u64,
    baz: bool,
}

This is saying that we have some structure named Foo. Foo has two fields: bar, a u64; and baz, a bool. To instantiate the structure Foo, we can use struct instantiation syntax, which is very similar to the declaration syntax except with expressions in place of types.

let foo = Foo {
    bar: 42,
    baz: false,
};

To access a field of a struct, use struct field access syntax:

// Instantiate `foo`.
let foo = Foo {
    bar: 42,
    baz: true,
};

// Access field `baz` of `foo`.
assert(foo.baz);

Struct Memory Layout

This information is not vital if you are new to the language, or programming in general.

Structs have zero memory overhead. What that means is that in memory, each struct field is laid out sequentially. No metadata regarding the struct's name or other properties is preserved at runtime. In other words, structs are compile-time constructs. This is the same in Rust, but different in other languages with runtimes like Java.

Tuples

Tuples are a basic static-length type which contain multiple different types within themselves. The type of a tuple is defined by the types of the values within it, and a tuple can contain basic types as well as structs and enums. An example of a tuple declaration is provided below.

let my_tuple: (u64, bool, u64) = (100, false, 10000);

The values within this tuple can then be accessed with a . syntax in order of the type, starting at an index of 0 like shown in the example provided below.

let x: u64 = my_tuple.0;
let y: bool = my_tuple.1;

Tuples can also contain tuples within themselves, and be used in destructing syntax to declare multiple values at once.

Common usecases for tuples are returning multiple values from a function, packing parameters into a function, or storing a series of related values.

Enums

Enumerations, or enums, are also known as sum types. An enum is a type that could be one of several variants. To declare an enum, you enumerate all potential variants. Let's look at enum declaration syntax:

enum Color {
    Blue: (),
    Green: (),
    Red: (),
    Silver: (),
    Grey: (),
}

Here, we have defined five potential colors. Each enum variant is just the color name. As there is no extra data associated with each variant, we say that each variant is of type (), or unit. It is also possible to have an enum variant contain extra data. Take a look at this more substantial example, which combines struct declarations with enum variants:

use std::collections::Vec;
use inventory_system::InventoryItem;
use inventory_system::Insurer;

struct Claim {
    insurance_company: Insurer,
    item_number: u64,
    item_cost: u64,
}

struct Receipt {
    customer: CustomerId,
    items_purchased: Vec<InventoryItem>,
}

struct Refund {
    customer: CustomerId,
    items_returned: Vec<InventoryItem>,
}

enum InventoryEvent {
    CustomerPurchase : Receipt,
    ItemLoss         : Claim,
    CustomerReturn   : Refund,
}
enum Color {
    Blue: (),
    Green: (),
    Red: (),
    Silver: (),
    Grey: (),
}

fn main() {
    let color = Color::Blue;
}

Here, we have instantiated a variable named color with enum instantiation syntax. Note that enum instantiation does not require the ~ tilde syntax. If we wanted to instantiate an enum with some interior data, it looks like this:

struct Claim {
    insurance_company: Insurer,
    item_number: u64,
    item_cost: u64,
}

let event = InventoryEvent::ItemLoss(Claim {
    insurance_company: ~Insurer::default(),
    item_number: 42,
    item_cost: 1_000,
});

Enum Memory Layout

This information is not vital if you are new to the language, or programming in general.

Enums do have some memory overhead. To know which variant is being represented, Sway stores a one-word (8-byte) tag for the enum variant. The space reserved after the tag is equivalent to the size of the largest enum variant. So, to calculate the size of an enum in memory, add 8 bytes to the size of the largest variant. For example, in the case of Color above, where the variants are all (), the size would be 8 bytes since the size of the largest variant is 0 bytes.

Methods and Associated Functions

Methods are similar to functions in that we declare them with the fn keyword and they have parameters and return a value. However, unlike functions, Methods are defined within the context of a struct (or enum), and either refers to that type or mutates it. The first parameter of a method is always self, which represents the instance of the struct the method is being called on.

Associated functions are very similar to methods, in that they are also defined in the context of a struct or enum, but they do not actually use any of the data in the struct and as a result do not take self as a parameter. Associated functions could be standalone functions, but they are included in a specific type for organizational or semantic reasons.

To declare methods and associated functions for a struct or enum, use an impl block. Here, impl stands for implementation.

script;

struct Foo {
    bar: u64,
    baz: bool,
}

impl Foo {
    // this is a _method_, as it takes `self` as a parameter.
    fn is_baz_true(self) -> bool {
        self.baz
    }

    // this is an _associated function_, since it does not take `self` as a parameter.
    fn new_foo(number: u64, boolean: bool) -> Foo {
        Foo {
            bar: number,
            baz: boolean,
        }
    }
}

fn main() {
    let foo = ~Foo::new_foo(42, true);
    assert(foo.is_baz_true());
}

Note the syntax of the associated function call: ~Foo::new_foo(42, true);. This bit of syntax is unique to Sway: when referring to a type directly, you preface the type with a tilde (~). To call an associated function, refer to the type and then the function name. To call a method, simply use dot syntax: foo.iz_baz_true().

Syntax Examples

enum Color {
    Blue: (),
    Green: (),
    Red: (),
    Silver: (),
    Grey: (),
    // etc...
}

enum Make {
    Ford: (),
    Toyota: (),
    Mazda: (),
    Chevrolet: (),
    BMW: (),
    // etc...
}
struct Car {
    make: CarMake,
    color: Color,
}

Comments and Logging

Comments

Comments in Sway start with two slashes and continue until the end of the line. For comments that extend beyond a single line, you'll need to include // on each line.

// hello world
// let's make a couple of lines
// commented.

You can also place comments at the ends of lines containing code.

fn main() {
    let baz = 8; // Eight is a lucky number
}

You can also do block comments

fn main() {
    /*
    You can write on multiple lines
    like this if you want
    */
    let baz = 8;
}

Logging

To log integers, you can use the log_u64, log_u32, log_u16, or log_u8 functions from the standard library.

use std::chain::log_u64;

fn main() {
    let baz = 8;
    log_u64(baz);
}

Note that you cannot log arbitrary structs yet because we do not yet support serialization.

Control Flow

if expressions

Sway supports if, else, and else if expressions that allow you to branch your code depending on conditions.

For example:

fn main() {
    let number = 6;

    if number % 4 == 0 {
        // do something
    } else if number % 3 == 0 {
        // do something else
    } else {
        // do something else
    }
}

Using if in a let statement

Like Rust, ifs are expressions in Sway. What this means is you can use if expressions on the right side of a let statement to assign the outcome to a variable.

let my_data = if some_bool < 10 { foo() } else { bar() };

Note that all branches of the if expression must return a value of the same type.

match expressions

Sway supports advanced pattern matching through exhaustive match expressions.

script;

fn foo() {
    // do something
}
fn bar() {
    // do something
}

fn main() -> u64 {
    let x = 5;

    // Match as an expression.
    let a = match 8 {
        7 => {
            4
        },
        9 => {
            5
        },
        8 => {
            6
        },
        _ => {
            100
        },
    };

    // Match as a statement for control flow.
    match x {
        5 => {
            foo()
        },
        _ => {
            bar()
        },
    };

    // Match as expression used for a return.
    match 42 {
        0 => {
            24
        },
        foo => {
            foo
        },
    }
}

Loops

while

Loops in Sway are currently limited to while loops. This is what they look like:

while counter < 10 {
    counter = counter + 1;
}

You need the while keyword, some condition (value < 10 in this case) which will be evaluated each iteration, and a block of code inside the curly braces ({...}) to execute each iteration.

break and continue

There are no break or continue keywords yet, but they're coming.

For now, the way to break out of a while loop early is to manually invalidate the condition. In this case, that just means setting counter to be >= 10.

Building on the previous example, here's what that might look like:

let mut counter = 0;
let mut break_early = false;
while counter < 10 {
    if break_early == true {
        // here we ensure the condition will evaluate to false, breaking the loop
        counter = 10
    } else {
        // calling some other function to set the bool value
        break_early = get_bool_value();
        counter = counter + 1;
    }
}

Nested loops

You can also use nested while loops if needed:

while condition_1 == true {
    // do stuff...
    while condition_2 == true {
        // do more stuff...
    }
}

Blockchain Development with Sway

Sway is fundamentally a blockchain language. Because of this, it has some features and requirements that you may not have seen in general-purpose programming languages.

These are also some concepts related to the FuelVM and Fuel ecosystem that you may utilize when writing Sway.

Hashing and Cryptography

The Sway standard library provides easy access to a selection of cryptographic hash functions (sha256 and Ethereum-compatible keccak256), and Ethereum-compatible secp256k1-based signature recovery operations.

Hashing

script;

use core::num::*;
use std::{hash::{keccak256, sha256}, logging::log};

const VALUE_A = 0x9280359a3b96819889d30614068715d634ad0cf9bba70c0f430a8c201138f79f;

enum Location {
    Earth: (),
    Mars: (),
}

struct Person {
    name: str[4],
    age: u64,
    alive: bool,
    location: Location,
    stats: Stats,
    some_tuple: (bool,
    u64), some_array: [u64;
    2],
    some_b256: b256,
}

struct Stats {
    strength: u64,
    agility: u64,
}

fn main() {
    let zero = ~b256::min();
    // Use the generic sha256 to hash some integers
    let sha_hashed_u8 = sha256(~u8::max());
    let sha_hashed_u16 = sha256(~u16::max());
    let sha_hashed_u32 = sha256(~u32::max());
    let sha_hashed_u64 = sha256(~u64::max());

    // Or hash a b256
    let sha_hashed_b256 = sha256(VALUE_A);

    // You can hash booleans too
    let sha_hashed_bool = sha256(true);

    // Strings are not a problem either
    let sha_hashed_str = sha256("Fastest Modular Execution Layer!");

    // Tuples of any size work too
    let sha_hashed_tuple = sha256((true, 7));

    // As do arrays
    let sha_hashed_array = sha256([4, 5, 6]);

    // Enums work too
    let sha_hashed_enum = sha256(Location::Earth);

    // Complex structs are not a problem
    let sha_hashed_struct = sha256(Person {
        name: "John", age: 9000, alive: true, location: Location::Mars, stats: Stats {
            strength: 10, agility: 9
        },
        some_tuple: (true, 8), some_array: [17, 76], some_b256: zero
    });

    log(sha_hashed_u8);
    log(sha_hashed_u16);
    log(sha_hashed_u32);
    log(sha_hashed_u64);
    log(sha_hashed_b256);
    log(sha_hashed_bool);
    log(sha_hashed_str);
    log(sha_hashed_tuple);
    log(sha_hashed_array);
    log(sha_hashed_enum);
    log(sha_hashed_struct);

    // Use the generic keccak256 to hash some integers
    let keccak_hashed_u8 = keccak256(~u8::max());
    let keccak_hashed_u16 = keccak256(~u16::max());
    let keccak_hashed_u32 = keccak256(~u32::max());
    let keccak_hashed_u64 = keccak256(~u64::max());

    // Or hash a b256
    let keccak_hashed_b256 = keccak256(VALUE_A);

    // You can hash booleans too
    let keccak_hashed_bool = keccak256(true);

    // Strings are not a problem either
    let keccak_hashed_str = keccak256("Fastest Modular Execution Layer!");

    // Tuples of any size work too
    let keccak_hashed_tuple = keccak256((true, 7));

    // As do arrays
    let keccak_hashed_array = keccak256([4, 5, 6]);

    // Enums work too
    let keccak_hashed_enum = keccak256(Location::Earth);

    // Complex structs are not a problem
    let keccak_hashed_struct = keccak256(Person {
        name: "John", age: 9000, alive: true, location: Location::Mars, stats: Stats {
            strength: 10, agility: 9
        },
        some_tuple: (true, 8), some_array: [17, 76], some_b256: zero
    });

    log(keccak_hashed_u8);
    log(keccak_hashed_u16);
    log(keccak_hashed_u32);
    log(keccak_hashed_u64);
    log(keccak_hashed_b256);
    log(keccak_hashed_bool);
    log(keccak_hashed_str);
    log(keccak_hashed_tuple);
    log(keccak_hashed_array);
    log(keccak_hashed_enum);
    log(keccak_hashed_struct);
}

Signature Recovery

script;

use std::result::Result;
use std::b512::B512;
use std::revert::revert;
use std::logging::log;
use std::ecr::{EcRecoverError, ec_recover, ec_recover_address};

const MSG_HASH = 0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323;

fn main() {
    let hi = 0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c;
    let lo = 0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d;
    let signature: B512 = ~B512::from(hi, lo);

    // A recovered public key pair.
    let public_key = ec_recover(signature, MSG_HASH);

    // A recovered Fuel address.
    let result_address: Result<Address, EcRecoverError> = ec_recover_address(signature, MSG_HASH);
    if let Result::Ok(address) = result_address {
        log(address.value);
    } else {
        revert(0);
    }
}

Note Recovery of EVM addresses is also supported via std::vm::evm.

Storage

When developing a smart contract, you will typically need some sort of persistent storage. In this case, persistent storage, often just called storage in this context, is a place where you can store values that are persisted inside the contract itself. This is in contrast to a regular value in memory, which disappears after the contract exits.

Put in conventional programming terms, contract storage is like saving data to a hard drive. That data is saved even after the program which saved it exits. That data is persistent. Using memory is like declaring a variable in a program: it exists for the duration of the program and is non-persistent.

Some basic use cases of storage include declaring an owner address for a contract and saving balances in a wallet.

Storage Accesses Via the storage Keyword

Declaring variables in storage requires a storage declaration that contains a list of all your variables and their types as follows:

storage {
    var1: Type1,
    var2: Type2,
    ...
}

To write into a storage variable, you need to use the storage keyword as follows:

storage.var1 = v;

To read a storage variable, you also need to use the storage keyword as follows:

let v = storage.var1;

Notes:

  • The only types currently supported by the syntax above are integers, Booleans, and structs.
  • The storage syntax cannot be used for mappings. Mappings need to be handled manually for now as shown in the Subcurrency example.
  • Storage, in general, is still work-in-progress and so, its use model may change in the future.

Manual Storage Management

Outside of the newer experimental storage syntax which is being stabalized, you can leverage FuelVM storage operations using the store and get methods provided in the standard library (std). Which currently works with primitive types.

With this approach you will have to manually assign the internal key used for storage.

An example is as follows:

contract;

use std::storage::{get, store};

abi StorageExample {
    fn store_something(amount: u64);
    fn get_something() -> u64;
}

const STORAGE_KEY: b256 = 0x0000000000000000000000000000000000000000000000000000000000000000;

impl StorageExample for Contract {
    fn store_something(amount: u64) {
        store(STORAGE_KEY, amount);
    }

    fn get_something() -> u64 {
        let value = get::<u64>(STORAGE_KEY);
        value
    }
}

Note, if you are looking to store non-primitive types (e.g. b256), please refer to this issue.

Purity

A function is pure if it does not access any persistent storage. Conversely, the function is impure if it does access any storage. Naturally, as storage is only available in smart contracts, impure functions cannot be used in predicates, scripts, or libraries. A pure function cannot call an impure function.

In Sway, functions are pure by default but can be opted into impurity via the storage function attribute. The storage attribute may take read and/or write arguments indicating which type of access the function requires.

#[storage(read)]
fn get_amount() -> u64 {
    ...
}

#[storage(read, write)]
fn increment_amount(increment: u64) -> u64 {
    ...
}

Impure functions which call other impure functions must have at least the same storage privileges or a superset of those for the function called. For example, to call a function with write access a caller must also have write access, or both read and write access. To call a function with read and write access the caller must also have both privileges.

The storage attribute may also be applied to methods and associated functions, trait and ABI declarations.

A pure function gives you some guarantees: you will not incur excessive storage gas costs, the compiler can apply additional optimizations, and they are generally easy to reason about and audit. A similar concept exists in Solidity. Note that Solidity refers to contract storage as contract state, and in the Sway/Fuel ecosystem, these two terms are largely interchangeable.

Identifiers

Addresses in Sway are similar to Ethereum addresses. The two major differences are:

  1. Sway addresses are 32 bytes long (instead of 20), and
  2. are computed with the SHA-256 hash of the public key instead of the keccak-256 hash.

Contracts, on the other hand, are uniquely identified with a contract ID rather than an address. A contract's ID is also 32 bytes long and is calculated here.

Native Support for Multiple Asset Types

The FuelVM has built-in support for working with multiple assets.

What does this mean in practice?

As in Ethereum, sending ETH to an address or contract is an operation built into the FuelVM, meaning it doesn't rely on the existence of some token smart contract to update balances to track ownership.

However, unlike Ethereum, the process for sending any native asset is the same. This means that while you would still need a smart contract to handle the minting and burning of fungible tokens, the sending and receiving of these tokens can be done independently of the token contract.

Liquidity Pool Example

All contracts in Fuel can mint and burn their own native token. Contracts can also receive and transfer any native asset including their own. Internal balances of all native assets pushed through calls or minted by the contract are tracked by the FuelVM and can be queried at any point using the balance_of function from the std library. Therefore, there is no need for any manual accounting of the contract's balances using persistent storage.

The std library provides handy methods for accessing Fuel's native assset operations.

In this example, we show a basic liquidity pool contract minting its own native asset LP token.

contract;

use std::{
    address::Address,
    assert::assert,
    context::call_frames::{contract_id, msg_asset_id},
    context::msg_amount,
    contract_id::ContractId,
    token::{mint_to_address, transfer_to_output}
};

abi LiquidityPool {
    fn deposit(recipient: Address);
    fn withdraw(recipient: Address);
}

const BASE_TOKEN: b256 = 0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c;

impl LiquidityPool for Contract {
    fn deposit(recipient: Address) {
        assert(msg_asset_id() == ~ContractId::from(BASE_TOKEN));
        assert(msg_amount() > 0);

        // Mint two times the amount.
        let amount_to_mint = msg_amount() * 2;

        // Mint some LP token based upon the amount of the base token.
        mint_to_address(amount_to_mint, recipient);
    }

    fn withdraw(recipient: Address) {
        assert(msg_asset_id() == contract_id());
        assert(msg_amount() > 0);

        // Amount to withdraw.
        let amount_to_transfer = msg_amount() / 2;

        // Transfer base token to recipient.
        transfer_to_output(amount_to_transfer, ~ContractId::from(BASE_TOKEN), recipient);
    }
}

Native Token Example

In this example, we show a native token contract with more minting, burning and transferring capabilities.

contract;

use std::{address::Address, assert::assert, context::*, contract_id::ContractId, token::*};

abi NativeAssetToken {
    fn mint_coins(mint_amount: u64);
    fn burn_coins(burn_amount: u64);
    fn force_transfer_coins(coins: u64, asset_id: ContractId, target: ContractId);
    fn transfer_coins_to_output(coins: u64, asset_id: ContractId, recipient: Address);
    fn deposit();
    fn get_balance(target: ContractId, asset_id: ContractId) -> u64;
    fn mint_and_send_to_contract(amount: u64, destination: ContractId);
    fn mint_and_send_to_address(amount: u64, recipient: Address);
}

impl NativeAssetToken for Contract {
    /// Mint an amount of this contracts native asset to the contracts balance.
    fn mint_coins(mint_amount: u64) {
        mint(mint_amount);
    }

    /// Burn an amount of this contracts native asset.
    fn burn_coins(burn_amount: u64) {
        burn(burn_amount);
    }

    /// Transfer coins to a target contract.
    fn force_transfer_coins(coins: u64, asset_id: ContractId, target: ContractId) {
        force_transfer(coins, asset_id, target);
    }

    /// Transfer coins to a transaction output to be spent later.
    fn transfer_coins_to_output(coins: u64, asset_id: ContractId, recipient: Address) {
        transfer_to_output(coins, asset_id, recipient);
    }

    /// Get the internal balance of a specific coin at a specific contract.
    fn get_balance(target: ContractId, asset_id: ContractId) -> u64 {
        balance_of(target, asset_id)
    }

    /// Deposit tokens back into the contract.
    fn deposit() {
        assert(msg_amount() > 0);
    }

    /// Mint and send this contracts native token to a destination contract.
    fn mint_and_send_to_contract(amount: u64, destination: ContractId) {
        mint_to_contract(amount, destination);
    }

    /// Mind and send this contracts native token to a destination address.
    fn mint_and_send_to_address(amount: u64, recipient: Address) {
        mint_to_address(amount, recipient);
    }
}

Access Control

Smart contracts require the ability to restrict access to and identify certain users or contracts. Unlike account-based blockchains, transactions in UTXO-based blockchains (i.e. Fuel) do not necessarily have a unique transaction sender. Additional logic is needed to handle this difference, and is provided by the standard library.

msg_sender

To deliver an experience akin to Ethereum's access control, the std library provides a msg_sender function, which identifies a unique caller based upon the call and/or transaction input data.

contract;

use std::{
    address::Address,
    assert::assert,
    chain::auth::{AuthError, Sender, msg_sender},
    result::*,
    revert::revert,
};

abi MyOwnedContract {
    fn receive(field_1: u64) -> bool;
}

const OWNER: b256 = 0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c;

impl MyOwnedContract for Contract {
    fn receive(field_1: u64) -> bool {
        let sender: Result<Sender, AuthError> = msg_sender();
        if let Sender::Address(addr) = sender.unwrap() {
            assert(addr.into() == OWNER);
        } else {
            revert(0);
        }

        true
    }
}

The msg_sender function works as follows:

  • If the caller is a contract, then Result::Ok(Sender) is returned with the ContractId sender variant.
  • If the caller is external (i.e. from a script), then all coin input owners in the transaction are checked. If all owners are the same, then Result::Ok(Sender) is returned with the Address sender variant.
  • If the caller is external and coin input owners are different, then the caller cannot be determined and a Result::Err(AuthError) is returned.

Calling Contracts

Smart contracts can be called by other contracts or scripts. In the FuelVM, this is done primarily with the call instruction.

Sway provides a nice way to manage callable interfaces with its abi system. The Fuel ABI specification can be found here.

Example

Here is an example of a contract calling another contract in Sway. A script can call a contract in the same way.

// ./contract_a.sw
contract;

abi ContractA {
    fn receive(field_1: bool, field_2: u64) -> u64;
}

impl ContractA for Contract {
    fn receive(field_1: bool, field_2: u64) -> u64 {
        assert(field_1 == true);
        assert(field_2 > 0);
        return_45()
    }
}

fn return_45() -> u64 {
  45
}
// ./contract_b.sw
contract;

use contract_a::ContractA;

abi ContractB {
    fn make_call();
}

const contract_id = 0x79fa8779bed2f36c3581d01c79df8da45eee09fac1fd76a5a656e16326317ef0;

impl ContractB for Contract {
    fn make_call() {
      let x = abi(ContractA, contract_id);
      let return_value = x.receive(true, 3); // will be 45
    }
}

NOTE The ABI is for external calls only therefore you cannot define a method in the ABI and call it in the same contract. If you want to define a function for a contract, but keep it private so that only your contract can call it, you can define it outside of the impl and call it inside the contract, similar to the return_45() function above.

Advanced Calls

All calls forward a gas stipend, and may additionally forward one native asset with the call.

Here is an example of how to specify the amount of gas (gas), the asset ID of the native asset (asset_id), and the amount of the native asset (amount) to forward:

script;

abi MyContract {
    fn foo(field_1: bool, field_2: u64);
}

fn main() {
    let x = abi(MyContract, 0x79fa8779bed2f36c3581d01c79df8da45eee09fac1fd76a5a656e16326317ef0);
    let asset_id = 0x7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777;
    x.foo {
        gas: 5000, asset_id: asset_id, amount: 5000
    }
    (true, 3);
}

Handling Re-entrancy

A common attack vector for smart contracts is re-entrancy. Similar to the Ethereum Virtual Machine, the FuelVM allows for re-entrancy.

A stateless re-entrancy guard is included in the Sway standard library. The guard will panic (revert) at run time if re-entrancy is detected.

contract;

use std::reentrancy::reentrancy_guard;

abi MyContract {
    fn some_method();
}

impl ContractB for Contract {
    fn some_method() {
        reentrancy_guard();
        // do something
    }
}

Differences from Ethereum

While the Fuel contract calling paradigm is similar to Ethereum's (using an ABI, forwarding gas and data), it differs in two key ways:

  1. Native assets: FuelVM calls can forward any native asset not just base asset.

  2. No data serialization: Contract calls in the FuelVM do not need to serialize data to pass it between contracts; instead they simply pass a pointer to the data. This is because the FuelVM has a shared global memory which all call frames can read from.

Forc

Forc stands for Fuel Orchestrator. Forc provides a variety of tools and commands for developers working with the Fuel ecosystem, such as scaffolding a new project, formatting, running scripts, deploying contracts, testing contracts, and more. If you're coming from a Rust background, forc is similar to cargo.

If you are new to Forc, see the Forc Project introduction section.

For a comprehensive overview of the Forc CLI commands, see the Commands section.

Manifest Reference

The Forc.toml (the manifest file) is a compulsory file for each package and it is written in [TOML] format. Forc.toml consists of the following fields:

  • [project] — Defines a sway project.

    • name — The name of the project.
    • authors — The authors of the project.
    • organization — The organization of the project.
    • license— The project license.
    • entry — The entry point for the compiler to start parsing from.
      • For the recomended way of selecting an entry point of large libraries please take a look at: Libraries
    • implicit_std - Controls whether provided std version (with the current forc version) will get added as a dependency implicitly. Unless you know what you are doing, leave this as default.
  • [dependencies] — Defines the dependencies.

  • [network] — Defines a network for forc to interact with.

    • url — URL of the network.

The [project] section

An example Forc.toml is shown below. Under [project] the following fields are optional:

  • authors
  • organization

Also for the following fields, a default value is provided so omitting them is allowed:

  • entry - (default : main.sw)
  • implicit_std - (default : true)
[project]
authors = ["user"]
entry = "main.sw"
organization = "Fuel_Labs"
license = "Apache-2.0"
name = "wallet_contract"

The [dependencies] section

The following fields can be provided with a dependency:

  • version - Desired version of the dependency
  • path - The path of the dependency (if it is local)
  • git - The URL of the git repo hosting the dependency
  • branch - The desired branch to fetch from the git repo
  • tag - The desired tag to fetch from the git repo
  • rev - The desired rev (i.e. commit hash) reference

Please see dependencies for details

The [network] section

For the following fields, a default value is provided so omitting them is allowed:

Dependencies

Forc has a dependency management system which can pull packages using git. This allows users to build and share Forc libraries.

Adding a dependency

If your Forc.toml doesn't already have a [dependencies] table, add one. Below, list the package name alongside its source. Currently, forc supports both git and path sources.

If a git source is specified, forc will fetch the git repository at the given URL and then search for a Forc.toml for a package with the given name anywhere inside the git repository.

The following example adds a library dependency named custom_lib. For git dependencies you may optionally specify a branch, tag, or rev (i.e. commit hash) reference.

[dependencies]
custom_lib = { git = "https://github.com/FuelLabs/custom_lib", branch = "master" }
# custom_lib = { git = "https://github.com/FuelLabs/custom_lib", tag = "v0.0.1" }
# custom_lib = { git = "https://github.com/FuelLabs/custom_lib", rev = "87f80bdf323e2d64e213895d0a639ad468f4deff" }

Depending on a local library using path:

[dependencies]
custom_lib = { path = "../custom_lib" }

Once the package is added, running forc build will automatically download added dependencies.

Updating dependencies

To update dependencies in your Forc directory you can run forc update. For path dependencies this will have no effect. For git dependencies with a branch reference, this will update the project to use the latest commit for the given branch.

Here are a list of commands available to forc:

forc-addr2line

Show location and context of an opcode address in its source file

USAGE:

forc addr2line [OPTIONS] --sourcemap-path <SOURCEMAP_PATH> --opcode-index <OPCODE_INDEX>

OPTIONS:

-c, --context <CONTEXT>

How many lines of context to show [default: 2]

-g, --sourcemap-path <SOURCEMAP_PATH>

Source file mapping in JSON format

-h, --help

Print help information

-i, --opcode-index <OPCODE_INDEX>

Opcode index

-s, --search-dir <SEARCH_DIR>

Where to search for the project root [default: .]

forc-build

Compile the current or target project.

The output produced will depend on the project's program type. Building script, predicate and contract projects will produce their bytecode in binary format <project-name>.bin. Building contracts and libraries will also produce the public ABI in JSON format <project-name>-abi.json.

USAGE:

forc build [OPTIONS]

OPTIONS:

-g, --debug-outfile <DEBUG_OUTFILE>

If set, outputs source file mapping in JSON format

-h, --help

Print help information

--locked

Requires that the Forc.lock file is up-to-date. If the lock file is missing, or it needs to be updated, Forc will exit with an error

--minify-json-abi

By default the JSON for ABIs is formatted for human readability. By using this option JSON output will be "minified", i.e. all on one line without whitespace

-o <BINARY_OUTFILE>

If set, outputs a binary file representing the script bytes

--offline

Offline mode, prevents Forc from using the network when managing dependencies. Meaning it will only try to use previously downloaded dependencies

--output-directory <OUTPUT_DIRECTORY>

The directory in which the sway compiler output artifacts are placed.

By default, this is <project-root>/out.

-p, --path <PATH>

Path to the project, if not specified, current working directory will be used

--print-finalized-asm

Whether to compile to bytecode (false) or to print out the generated ASM (true)

--print-intermediate-asm

Whether to compile to bytecode (false) or to print out the generated ASM (true)

--print-ir

Whether to compile to bytecode (false) or to print out the generated IR (true)

-s, --silent

Silent mode. Don't output any warnings or errors to the command line

EXAMPLE:

Compile the sway files of the current project.

$ forc build
Compiled script "my-fuel-project".
Bytecode size is 28 bytes.

The output produced will depend on the project's program type. Building script, predicate and contract projects will produce their bytecode in binary format <project-name>.bin. Building contracts and libraries will also produce the public ABI in JSON format <project-name>-abi.json.

By default, these artifacts are placed in the out/ directory.

If a Forc.lock file did not yet exist, it will be created in order to pin each of the dependencies listed in Forc.toml to a specific commit or version.

forc-clean

Removes the default forc compiler output artifact directory, i.e. <project-name>/out. Also calls cargo clean which removes the target directory generated by cargo when running tests

USAGE:

forc clean [OPTIONS]

OPTIONS:

-h, --help

Print help information

-p, --path <PATH>

Path to the project, if not specified, current working directory will be used

forc-completions

Generate tab-completion scripts for your shell

USAGE:

forc completions --shell

OPTIONS:

-h, --help

Print help information

-s, --shell <SHELL>

Specify shell to enable tab-completion for

[possible values: zsh, bash, fish, powershell, elvish]

For more info: https://fuellabs.github.io/sway/latest/forc/commands/forc_completions.html

DISCUSSION:

Enable tab completion for Bash, Fish, Zsh, or PowerShell The script is output on stdout, allowing one to re-direct the output to the file of their choosing. Where you place the file will depend on which shell, and which operating system you are using. Your particular configuration may also determine where these scripts need to be placed.

Here are some common set ups for the three supported shells under Unix and similar operating systems (such as GNU/Linux).

BASH:

Completion files are commonly stored in /etc/bash_completion.d/ for system-wide commands, but can be stored in ~/.local/share/bash-completion/completions for user-specific commands. Run the command:

mkdir -p ~/.local/share/bash-completion/completions
forc completions --shell=bash >> ~/.local/share/bash-completion/completions/forc

This installs the completion script. You may have to log out and log back in to your shell session for the changes to take effect.

BASH (macOS/Homebrew):

Homebrew stores bash completion files within the Homebrew directory. With the bash-completion brew formula installed, run the command:

mkdir -p $(brew --prefix)/etc/bash_completion.d
forc completions --shell=bash > $(brew --prefix)/etc/bash_completion.d/forc.bash-completion

FISH:

Fish completion files are commonly stored in $HOME/.config/fish/completions. Run the command:

mkdir -p ~/.config/fish/completions
forc completions --shell=fish > ~/.config/fish/completions/forc.fish

This installs the completion script. You may have to log out and log back in to your shell session for the changes to take effect.

ZSH:

ZSH completions are commonly stored in any directory listed in your $fpath variable. To use these completions, you must either add the generated script to one of those directories, or add your own to this list.

Adding a custom directory is often the safest bet if you are unsure of which directory to use. First create the directory; for this example we'll create a hidden directory inside our $HOME directory:

mkdir ~/.zfunc

Then add the following lines to your .zshrc just before compinit:

fpath+=~/.zfunc

Now you can install the completions script using the following command:

forc completions --shell=zsh > ~/.zfunc/_forc

You must then either log out and log back in, or simply run

exec zsh

for the new completions to take effect.

CUSTOM LOCATIONS:

Alternatively, you could save these files to the place of your choosing, such as a custom directory inside your $HOME. Doing so will require you to add the proper directives, such as sourceing inside your login script. Consult your shells documentation for how to add such directives.

POWERSHELL:

The powershell completion scripts require PowerShell v5.0+ (which comes with Windows 10, but can be downloaded separately for windows 7 or 8.1).

First, check if a profile has already been set

Test-Path $profile

If the above command returns False run the following

New-Item -path $profile -type file -force

Now open the file provided by $profile (if you used the New-Item command it will be ${env:USERPROFILE}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

Next, we either save the completions file into our profile, or into a separate file and source it inside our profile. To save the completions into our profile simply use

forc completions --shell=powershell >> ${env:USERPROFILE}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

forc-deploy

Deploy contract project. Crafts a contract deployment transaction then sends it to a running node

USAGE:

forc deploy [OPTIONS]

OPTIONS:

-g, --debug-outfile <DEBUG_OUTFILE>

If set, outputs source file mapping in JSON format

-h, --help

Print help information

--locked

Requires that the Forc.lock file is up-to-date. If the lock file is missing, or it needs to be updated, Forc will exit with an error

--minify-json-abi

By default the JSON for ABIs is formatted for human readability. By using this option JSON output will be "minified", i.e. all on one line without whitespace

-o <BINARY_OUTFILE>

If set, outputs a binary file representing the script bytes

--offline

Offline mode, prevents Forc from using the network when managing dependencies. Meaning it will only try to use previously downloaded dependencies

--output-directory <OUTPUT_DIRECTORY>

The directory in which the sway compiler output artifacts are placed.

By default, this is <project-root>/out.

-p, --path <PATH>

Path to the project, if not specified, current working directory will be used

--print-finalized-asm

Whether to compile to bytecode (false) or to print out the generated ASM (true)

--print-intermediate-asm

Whether to compile to bytecode (false) or to print out the generated ASM (true)

--print-ir

Whether to compile to bytecode (false) or to print out the IR (true)

-s, --silent

Silent mode. Don't output any warnings or errors to the command line

-u, --url <URL>

The node url to deploy, if not specified uses DEFAULT_NODE_URL. If url is specified overrides network url in manifest file (if there is one)

EXAMPLE:

You can use forc deploy, which triggers a contract deployment transaction and sends it to a running node.

Alternatively, you can deploy your Sway contract programmatically using fuels-rs, our Rust SDK.

You can find an example within our fuels-rs book.

forc-init

Create a new Forc project

USAGE:

forc init [OPTIONS] <PROJECT_NAME>

ARGS:

<PROJECT_NAME>

The name of your project

OPTIONS:

--contract

The default program type, excluding all flags or adding this flag creates a basic contract program

-h, --help

Print help information

--library

Adding this flag creates an empty library program

--predicate

Adding this flag creates an empty predicate program

--script

Adding this flag creates an empty script program

EXAMPLE:

$ forc init my-fuel-project
$ cd my-fuel-project
$ tree
.
├── Cargo.toml
├── Forc.toml
├── src
│   └── main.sw
└── tests
    └── harness.rs

Forc.toml is the Forc manifest file, containing information about the project and dependencies. Cargo.toml is the Rust project manifest file, used by the Rust-based tests package.

A src/ directory is created, with a single main.sw Sway file in it.

A tests/ directory is also created. The Cargo.toml in the root directory contains necessary Rust dependencies to enable you to write Rust-based tests using our Rust SDK (fuels-rs). More on this in the Test section down below.

forc-json-abi

Output the JSON associated with the ABI

USAGE:

forc json-abi [OPTIONS]

OPTIONS:

-h, --help

Print help information

--minify

By default the JSON for ABIs is formatted for human readability. By using this option JSON output will be "minified", i.e. all on one line without whitespace

-o <JSON_OUTFILE>

If set, outputs a json file representing the output json abi

--offline

Offline mode, prevents Forc from using the network when managing dependencies. Meaning it will only try to use previously downloaded dependencies

-p, --path <PATH>

Path to the project, if not specified, current working directory will be used

-s, --silent

Silent mode. Don't output any warnings or errors to the command line

forc-parse-bytecode

Parse bytecode file into a debug format

USAGE:

forc parse-bytecode <FILE_PATH>

ARGS:

<FILE_PATH>

OPTIONS:

-h, --help

Print help information

EXAMPLE:

We can try this command with the initial project created using forc init, with the counter template:

forc init --template counter counter
cd counter
forc build -o obj
counter$ forc parse-bytecode obj

  half-word   byte   op                   raw           notes
          0   0      JI(4)                90 00 00 04   conditionally jumps to byte 16
          1   4      NOOP                 47 00 00 00
          2   8      Undefined            00 00 00 00   data section offset lo (0)
          3   12     Undefined            00 00 00 c8   data section offset hi (200)
          4   16     LW(63, 12, 1)        5d fc c0 01
          5   20     ADD(63, 63, 12)      10 ff f3 00
         ...
         ...
         ...
         60   240    Undefined            00 00 00 00
         61   244    Undefined            fa f9 0d d3
         62   248    Undefined            00 00 00 00
         63   252    Undefined            00 00 00 c8

forc-plugins

Find all forc plugins available via PATH.

Prints information about each discovered plugin.

USAGE:

forc plugins [OPTIONS]

OPTIONS:

-d, --describe

Prints the long description associated with each listed plugin

-h, --help

Print help information

-p, --paths

Prints the absolute path to each discovered plugin

forc-run

Run script project. Crafts a script transaction then sends it to a running node

USAGE:

forc run [OPTIONS] [NODE_URL]

ARGS:

<NODE_URL> URL of the Fuel Client Node

[env: FUEL_NODE_URL=] [default: http://127.0.0.1:4000]

OPTIONS:

--byte-price <BYTE_PRICE>

Set the transaction byte price. Defaults to 0

--contract <CONTRACT>

32-byte contract ID that will be called during the transaction

-d, --data <DATA>

Hex string of data to input to script

--dry-run

Only craft transaction and print it out

-g, --debug-outfile <DEBUG_OUTFILE>

If set, outputs source file mapping in JSON format

--gas-limit <GAS_LIMIT>

Set the transaction gas limit. Defaults to the maximum gas limit

--gas-price <GAS_PRICE>

Set the transaction gas price. Defaults to 0

-h, --help

Print help information

-k, --kill-node

Kill Fuel Node Client after running the code. This is only available if the node is started from forc run

--locked

Requires that the Forc.lock file is up-to-date. If the lock file is missing, or it needs to be updated, Forc will exit with an error

--minify-json-abi

By default the JSON for ABIs is formatted for human readability. By using this option JSON output will be "minified", i.e. all on one line without whitespace

-o <BINARY_OUTFILE>

If set, outputs a binary file representing the script bytes

--output-directory <OUTPUT_DIRECTORY>

The directory in which the sway compiler output artifacts are placed.

By default, this is <project-root>/out.

-p, --path <PATH>

Path to the project, if not specified, current working directory will be used

--print-finalized-asm

Whether to compile to bytecode (false) or to print out the generated ASM (true)

--print-intermediate-asm

Whether to compile to bytecode (false) or to print out the generated ASM (true)

--print-ir

Whether to compile to bytecode (false) or to print out the IR (true)

-r, --pretty-print

Pretty-print the outputs from the node

-s, --silent

Silent mode. Don't output any warnings or errors to the command line

forc-test

Run Rust-based tests on current project. As of now, forc test is a simple wrapper on cargo test; forc init also creates a rust package under your project, named tests. You can opt to either run these Rust tests by using forc test or going inside the package and using cargo test

USAGE:

forc test [OPTIONS] [TEST_NAME] [-- <CARGO_TEST_ARGS>...]

ARGS:

<TEST_NAME> If specified, only run tests containing this string in their names

<CARGO_TEST_ARGS>

.. All trailing arguments following -- are collected within this argument.

E.g. Given the following:

forc test -- foo bar baz

The arguments foo, bar and baz are forwarded on to cargo test like so:

cargo test -- foo bar baz

OPTIONS:

--cargo-test-opts <CARGO_TEST_OPTS>

Options passed through to the cargo test invocation.

E.g. Given the following:

forc test --cargo-test-opts="--color always"

The --color always option is forwarded to cargo test like so:

cargo test --color always

-h, --help

Print help information

EXAMPLE:

You can write tests in Rust using our Rust SDK. These tests can be run using forc test, which will look for Rust tests under the tests/ directory (which is created automatically with forc init).

You can find an example under the Testing with Rust section.

forc-update

Update dependencies in the Forc dependencies directory

USAGE:

forc update [OPTIONS]

OPTIONS:

-c, --check

Checks if the dependencies have newer versions. Won't actually perform the update, will output which ones are up-to-date and outdated

-d <TARGET_DEPENDENCY>

Dependency to be updated. If not set, all dependencies will be updated

-h, --help

Print help information

-p, --path <PATH>

Path to the project, if not specified, current working directory will be used

forc-template

Create a new Forc project from a git template

USAGE:

forc template [OPTIONS] <PROJECT_NAME>

ARGS:

<PROJECT_NAME>

The name of the project that will be created

OPTIONS:

-h, --help

Print help information

-t, --template-name <TEMPLATE_NAME>

The name of the template that needs to be fetched and used from git repo provided

-u, --url <URL>

The template url, should be a git repo [default: https://github.com/fuellabs/sway]

EXAMPLE:

forc template --url https://github.com/owner/template/ --project_name my_example_project

The command above fetches the HEAD of the template repo and searchs for Forc.toml at the root of the fetched repo. It will fetch the repo and preapare a new Forc.toml with new project name. Outputs everthing to current_dir/project_name.

forc template --url https://github.com/FuelLabs/sway/ --template_name counter --project_name my_example_project

The command above fetches the HEAD of the sway repo and searchs for counter example inside it (There is an example called counter under sway/examples). It will fetch the counter example and preapare a new Forc.toml with new project name. Outputs everthing to current_dir/project_name..

Plugins

Plugins can be used to extend forc with new commands that go beyond the native commands mentioned in the previous chapter. While the Fuel ecosystem provides a few commonly useful plugins (forc-fmt, forc-lsp, forc-explore), anyone can write their own!

Let's install a starter plugin, forc-gm, and take a look at how it works underneath:

cargo install forc-gm

Check that we have installed forc-gm:

$ forc plugins
/Users/<USER>/.cargo/bin/forc-gm

Underneath, forc-gm is a simple CLI app, with clap as the only dependency:

//! A sample `forc` plugin that greets you!
//!
//! Once installed and available via `PATH`, can be executed via `forc gm`.

use clap::Parser;

#[derive(Debug, Parser)]
#[clap(
    name = "forc-gm",
    about = "Sample Forc plugin that greets you!",
    version
)]
struct App {
    #[clap(subcommand)]
    pub subcmd: Option<Subcommand>,
}

#[derive(Debug, Parser)]
enum Subcommand {
    /// Say 'gm' to Fuel!
    Fuel,
}

fn main() {
    let app = App::parse();

    match app.subcmd {
        Some(Subcommand::Fuel) => greet_fuel(),
        None => greet(),
    }
}

fn greet_fuel() {
    println!("gn from Fuel!");
}

fn greet() {
    println!("gn!");
}

You can say gm, or you can greet Fuel:

$ forc gm
gn!
$ forc gm fuel
gn from Fuel!

Writing your own plugin

We encourage anyone to write and publish their own forc plugin to enhance their development experience.

Your plugin must be named in the format forc-<MY_PLUGIN> and you may use the above template as a starting point. You can use clap and add more subcommands, options and configurations to suit your plugin's needs.

forc-explore

Forc plugin for running the Fuel Block Explorer.

USAGE:

forc-explore [OPTIONS] [SUBCOMMAND]

OPTIONS:

-h, --help

Print help information

-p, --port <PORT>

The port number at which the explorer will run on localhost [default: 3030]

-V, --version

Print version information

SUBCOMMANDS:

clean

Cleans up any existing state associated with the fuel block explorer

help

Print this message or the help of the given subcommand(s)

forc-fmt

Forc plugin for running the Sway code formatter.

USAGE:

forc-fmt [OPTIONS]

OPTIONS:

-c, --check

Run in 'check' mode.

  • Exits with 0 if input is formatted correctly. - Exits with 1 and prints a diff if formatting is required.

-h, --help

Print help information

-p, --path <PATH>

Path to the project, if not specified, current working directory will be used

-V, --version

Print version information

forc-lsp

Forc plugin for the Sway LSP (Language Server Protocol) implementation.

USAGE:

forc-lsp [OPTIONS]

OPTIONS:

-h, --help

Print help information

--parsed-tokens-as-warnings

Instructs the client to draw squiggly lines under all of the tokens that our server managed to parse

-V, --version

Print version information

Testing

Testing your Sway contracts can be done with the Rust SDK.

Testing with Rust

If you look again at the project structure when you create a new Forc project with forc init, you can see a directory called tests/:

$ forc init my-fuel-project
$ cd my-fuel-project
$ tree .
├── Cargo.toml
├── Forc.toml
├── src
│   └── main.sw
└── tests
    └── harness.rs

Note that this is also a Rust package, hence the existence of a Cargo.toml (Rust manifest file) in the project root directory. The Cargo.toml in the root directory contains necessary Rust dependencies to enable you to write Rust-based tests using our Rust SDK, (fuels-rs).

These tests can be run using forc test which will look for Rust tests under the tests/ directory (created automatically with forc init).

For example, let's write tests against the following contract, written in Sway. This can be done in the pregenerated src/main.sw or in a new file in src. In the case of the latter, update the entry field in Forc.toml to point at the new contract.

contract;

abi TestContract {
    fn initialize_counter(value: u64) -> u64;
    fn increment_counter(amount: u64) -> u64;
}

storage {
    counter: u64,
}

impl TestContract for Contract {
    fn initialize_counter(value: u64) -> u64 {
        storage.counter = value;
        value
    }

    fn increment_counter(amount: u64) -> u64 {
        let incremented = storage.counter + amount;
        storage.counter = incremented;
        incremented
    }
}

Our tests/harness.rs file could look like:

use fuel_tx::{ContractId, Salt};
use fuels::prelude::*;
use fuels::test_helpers;
use fuels_abigen_macro::abigen;

// Load abi from json
abigen!(MyContract, "out/debug/my-fuel-project-abi.json");

async fn get_contract_instance() -> (MyContract, ContractId) {
    // Deploy the compiled contract
    let salt = Salt::from([0u8; 32]);
    let compiled = Contract::load_sway_contract("./out/debug/my-fuel-project.bin", salt).unwrap();

    // Launch a local network and deploy the contract
    let (provider, wallet) = test_helpers::setup_test_provider_and_wallet().await;

    let id = Contract::deploy(&compiled, &provider, &wallet, TxParameters::default())
        .await
        .unwrap();

    let instance = MyContract::new(id.to_string(), provider, wallet);

    (instance, id)
}

#[tokio::test]
async fn can_get_contract_id() {
    let (contract_instance, _id) = get_contract_instance().await;

    // Call `initialize_counter()` method in our deployed contract.
    // Note that, here, you get type-safety for free!
    let result = contract_instance
        .initialize_counter(42)
        .call()
        .await
        .unwrap();

    assert_eq!(42, result.value);

    // Call `increment_counter()` method in our deployed contract.
    let result = contract_instance
        .increment_counter(10)
        .call()
        .await
        .unwrap();

    assert_eq!(52, result.value);
    // Now you have an instance of your contract you can use to test each function
}

Then, in the root of our project, running forc test will run the test above, compiling and deploying the contract to a local Fuel network, and calling the ABI methods against the contract deployed in there:

$ forc test

running 1 test
test harness ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.64s

Advanced Concepts

Advanced concepts.

Generic Types

Basics

In Sway, generic types follow a very similar pattern to those in Rust. Let's look at some example syntax, starting with a generic function:

fn noop<T>(argument: T) -> T {
    argument
}

Here, the noop() function trivially returns exactly what was given to it. T is a type parameter, and it says that this function exists for all types T. More formally, this function could be typed as:

noop :: ∀T. T -> T

Generic types are a way to refer to types in general, meaning without specifying a single type. Our noop function would work with any type in the language, so we don't need to specify noop(argument: u8) -> u8, noop(argument: u16) -> u16, etc.

Code Generation

One question that arises when dealing with generic types is: how does the assembly handle this? There are a few approaches to handling generic types at the lowest level. Sway uses a technique called monomorphization. This means that the generic function is compiled to a non-generic version for every type it is called on. In this way, generic functions are purely shorthand for the sake of ergonomics.

Trait Constraints

Important background to know before diving into trait constraints is that the where clause can be used to specify the required traits for the generic argument. So, when writing something like a HashMap you may want to specify that the generic argument implements a Hash trait.

fn get_hashmap_key<T>(Key : T) -> b256
    where T: Hash
{
    // Code within here can then call methods associated with the Hash trait on Key
}

where clauses are still work-in-progress, so some where statements shown may not be fully implemented.

Of course, our noop() function is not useful. Often, a programmer will want to declare functions over types which satisfy certain traits. For example, let's try to implement the successor function, successor(), for all numeric types.

fn successor<T>(argument: T)
    where T: Add
{
    argument + 1
}

Run forc build, and you will get:

.. |
 9 |   where T: Add
10 |   {
11 |     argument + 1                                        
   |                ^ Mismatched types: expected type "T" but saw type "u64"
12 |   }
13 |

This is because we don't know for a fact that 1, which in this case defaulted to 1u64, actually can be added to T. What if T is f64? Or b256? What does it mean to add 1u64 in these cases?

We can solve this problem with another trait constraint. We can only find the successor of some value of type T if that type T defines some incrementor. Let's make a trait:

trait Incrementable {
    /// Returns the value to add when calculating the successor of a value.
    fn incrementor() -> Self;
}

Now, we can modify our successor() function:

fn successor<T>(argument: T)
    where T: Add,
          T: Incrementable
{
    argument + ~T::incrementor()
}

(There's a little bit of new syntax here. When directly referring to a type to execute a method from it, a tilde (~) is used. This may change in the future.)

Generic Structs and Enums

Just like functions, structs and enums can be generic. Let's take a look at the standard library version of Option<T>:

enum Option<T> {
    Some: T,
    None: (),
}

Just like an unconstrained generic function, this type exists for all (∀) types T. Result<T, E> is another example:

enum Result<T, E> {
    Ok: T,
    Err: E,
}

Both generic enums and generic structs can be trait constrained, as well. Consider this struct:

struct Foo<T>
    where T: Add
{
    field_one: T,
}

Type Arguments

Similar to Rust, Sway has what is colloquially known as the turbofish. The turbofish looks like this: ::<> (see the little fish with bubbles behind it?). The turbofish is used to annotate types in a generic context. Say you have the following function:

fn foo<T, E>(t: T) -> Result<T, E> {
    Result::Ok(t)
}

In this code example, which is admittedly asinine, you can't possibly know what type E is. You'd need to provide the type manually, with a turbofish:

fn foo<T, E>(t: T) -> Result<T, E> {
    Result::Ok::<T, MyErrorType>(t)
}

It is also common to see the turbofish used on the function itself:

fn main() {
    foo::<Bar, Baz>()
}

Traits

Declaring a Trait

A trait opts a type into a certain type of behavior or functionality that can be shared among types. This allows for easy reuse of code and generic programming. If you have ever used a typeclass in Haskell, a trait in Rust, or even an interface in Java, these are similar concepts.

Let's take a look at some code:

trait Compare {
    fn equals(self, b: Self) -> bool;
} {
    fn not_equals(self, b: Self) -> bool {
        !self.equals(b)
    }
}

We have just declared a trait called Compare. After the name of the trait, there are two blocks of code (a block is code enclosed in { curly brackets }). The first block is the interface surface. The second block is the methods provided by the trait. If a type can provide the methods in the interface surface, then it gets access to the methods in the trait for free! What the above trait is saying is: if you can determine if two values are equal, then for free, you can determine that they are not equal. Note that trait methods have access to the methods defined in the interface surface.

Implementing a Trait

Ok, so I know that numbers can be equal. I want to implement my Compare trait for u64. Let's take a look at how that is done:

impl Compare for u64 {
    fn equals(self, b: Self) -> bool {
        self == b
    }
}

The above snippet declares all of the methods in the trait Compare for the type u64. Now, we have access to both the equals and not_equals methods for u64, as long as the trait Compare is in scope.

Supertraits

When using multiple traits, scenarios often come up where one trait may require functionality from another trait. This is where supertraits come in as they allow you to require a trait when implementing another trait (ie. a trait with a trait). A good example of this is the Ord trait of the core library of Sway. The Ord trait requires the Eq trait, so Eq is kept as a separate trait as one may decide to implement Eq without implementing other parts of the Ord trait.


trait Eq {
    fn equals(self, b: Self) -> bool;
}

trait Ord: Eq {
    fn gte(self, b: Self) -> bool;
}

impl Ord for u64 {
    fn gte(self, b: Self) -> bool {
        // As `Eq` is a supertrait of `Ord`, `Ord` can access the equals method
        self.equals(b) || self.gt(b)
    }
}

To require a supertrait, add a : after the trait name and then list the traits you would like to require and separate them with a +.

Use Cases

Custom Types (structs, enums)

Often, libraries and APIs have interfaces that are abstracted over a type that implements a certain trait. It is up to the consumer of the interface to implement that trait for the type they wish to use with the interface. For example, let's take a look at a trait and an interface built off of it.

library games;

pub enum Suit {
    Hearts: (),
    Diamonds: (),
    Clubs: (),
    Spades: (),
}

pub trait Card {
    fn suit(self) -> Suit;
    fn value(self) -> u8;
}

fn play_game_with_deck<T>(a: Vec<T>) where T: Card {
    // insert some creative card game here
}

Now, if you want to use the function play_game_with_deck with your struct, you must implement Card for your struct. Note that the following code example assumes a dependency games has been included in the Forc.toml file.

script;

use games::*;

struct MyCard {
    suit: Suit,
    value: u8
}

impl Card for MyCard {
    fn suit(self) -> Suit {
        self.suit
    }
    fn value(self) -> u8 {
        self.value
    }
}

fn main() {
    let mut i = 52;
    let mut deck: Vec<MyCard> = Vec::with_capacity(50);
    while i > 0 {
        i = i - 1;
        deck.push(MyCard { suit: generate_random_suit(), value: i % 4}
    }
    play_game_with_deck(deck);
}

fn random_suit(i: u64) -> Suit {
  [ ... ]
}

Trait Constraints

Trait constraints on generics are currently a work in progress.

Inline Assembly in Sway

While many users will never have to touch assembly language while writing sway code, it is a powerful tool that enables many advanced use-cases (ie: optimizations, building libraries, etc).

ASM Block

In Sway, the way we use assembly inline is to declare an asm block like this:

asm() {...}

Declaring an asm block is similar to declaring a function. We can specify register names to operate on as arguments, we can perform operations within the block, and we can return a value. Here's an example showing what this might look like:

pub fn add_1(num: u32) -> u32 {
    asm(r1: num, r2) {
        add r2 r1 one;
        r2: u32
    }
}

An asm block can only return a single register. If you really need to return more than one value, you can modify a tuple. Here's an example showing how can implement this (u64, u64):

script;

use std::assert::assert;

fn adder(a: u64, b: u64, c: u64) -> (u64, u64) {
    let empty_tuple = (0u64, 0u64);
    asm(output: empty_tuple, r1: a, r2: b, r3: c, r4, r5) {
        add r4 r1 r2; // add a & b and put the result in r4
        add r5 r2 r3; // add b & c and put the result in r5
        sw output r4 i0; // store the word in r4 in output + 0 words
        sw output r5 i1; // store the word in r5 in output + 1 word
        output: (u64, u64) // return both values
    }
}

fn main() -> bool {
    let(first, second) = adder(1, 2, 3);
    assert(first == 3);
    assert(second == 5);
    true
}

Note that this is contrived example meant to demonstrate the syntax; there's absolutely no need to use assembly to add integers!

Note that in the above example:

  • we initialized the register r1 with the value of num.
  • we declared a second register r2 (you may choose any register names you want).
  • we use the add opcode to add one to the value of r1 and store it in r2.
  • one is an example of a "reserved register", of which there are 16 in total. Further reading on this is linked below under "Semantics".
  • we return r2 & specify the return type as being u32 (the return type is u64 by default).

An important note is that the ji and jnei opcodes are not available within an asm block. For those looking to introduce control flow to asm blocks, it is recommended to surround smaller chunks of asm with control flow (if, else, and while).

For examples of assembly in action, check out the Sway standard library.

For a complete list of all instructions supported in the FuelVM: Instructions.

And to learn more about the FuelVM semantics: Semantics.

Application Frontend Development

TypeScript SDK

The TypeScript SDK supports common tasks like:

  • Deploying and calling contracts
  • Generating contract types with TypeChain
  • Building and sending transactions
  • Encoding and decoding contract ABI

Refer here for full documentation.

Reference

Style Guide

Capitalization

In Sway, structs, traits, and enums are CapitalCase. Modules, variables, and functions are snake_case, constants are SCREAMING_SNAKE_CASE. The compiler will warn you if your capitalization is ever unidiomatic.

Temporary Workarounds

Storage Variables and Mappings

Storage variables of types str[], b256, enum, and arrays are not yet supported. After this issue is closed, it will be possible to read and write these types using manual storage management. Moreover, storage mappings have to be managed manually for now as shown in the Subcurrency example.

Optimizer

The optimizing pass of the compiler is not yet implemented, therefore bytecode will be more expensive and larger than it would be in production. Note that eventually the optimizer will support zero-cost abstractions, avoiding the need for developers to go down to inline assembly to produce optimal code.

Formatter

Currently, we need to parse the Sway code before formatting it. Hence, the formatter cannot work on Sway code that does not parse correctly. This requirement may be changed in the future.

Differences From Solidity

This page outlines some of the critical differences between Sway and Solidity, and between the FuelVM and the EVM.

Underlying Virtual Machine

The underlying virtual machine targeted by Sway is the FuelVM, specified here. Solidity targets the Ethereum Virtual Machine (EVM), specified here.

Word Size

Words in the FuelVM are 64 bits (8 bytes), rather than the 256 bits (32 bytes) of the EVM. Therefore, primitive integers only go up to u64, and hashes (the b256 type) are not in registers but rather in memory. A b256 is therefore a pointer to a 32-byte memory region containing the hash value.

Unsigned Integers Only

Only unsigned integers are provided as primitives: u8, u16, u32, and u64. Signed integer arithmetic is not available in the FuelVM. Signed integers and signed integer arithmetic can be implemented in high-level libraries if needed.

Global Revert

Panics in the FuelVM (called "reverts" in Solidity and the EVM) are global, i.e. they cannot be caught. A panic will completely and unconditionally revert the stateful effects of a transaction, minus gas used.

Default Safe Math

Math in the FuelVM is by default safe (i.e. any overflow or exception is a panic). Safety checks are performed natively in the VM implementation, rather than at the bytecode level like Solidity's default safe math.

No* Code Size Limit

There is no practical code size limit to Sway contracts. The physical limit is governed by the VM_MAX_RAM VM parameter, which at the time of writing is 64 MiB.

Differences From Rust

Sway shares a lot with Rust, especially its syntax. Because they are so similar, you may be surprised or caught off guard when they differ. This page serves to outline, from a high level, some of the syntactic gotchas that you may encounter.

Enum Variant Syntax

In Rust, enums generally take one of three forms: unit variants, which have no inner data, struct variants, which contain named fields, and tuple variants, which contain within them a tuple of data. If you are unfamiliar with these terms, this is what they look like:

// note to those skimming the docs: this is Rust syntax! Not Sway! Don't copy/paste this into a Sway program.

enum Foo {
    UnitVariant,
    TupleVariant(u32, u64, bool),
    StructVariant {
        field_one: bool,
        field_two: bool
    }
}

In Sway, enums are simplified. Enums variants must all specify exactly one type. This type represents their interior data. This is actually isomorphic to what Rust offers, just with a different syntax. I'll now rewrite the above enum but with Sway syntax:

// This is equivalent Sway syntax for the above Rust enum.
enum Foo {
    UnitVariant: (),
    TupleVariant: (u32, u64, bool),
    StructVariant: MyStruct,
}

struct MyStruct {
    field_one: bool,
    field_two: bool,
}

Contributing To Sway

Thanks for your interest in contributing to Sway! This document outlines the process for installing and setting up the Sway toolchain for development, as well as some conventions on contributing to Sway.

If you run into any difficulties getting started, you can always ask questions on our Discord.

Building and setting up a development workspace

See the introduction section for instructions on installing and setting up the Sway toolchain.

Getting the repository

  1. Visit the Sway repo and fork the project.
  2. Then clone your forked copy to your local machine and get to work.
git clone https://github.com/FuelLabs/sway
cd sway

Building and testing

The following steps will run the sway test suite and ensure that everything is set up correctly.

First, open a new terminal and start fuel-core with:

fuel-core

Then open a second terminal, cd into the sway repo and run:

cargo run --bin test

After the test suite runs, you should see:

Tests passed.
_n_ tests run (0 skipped)

Congratulations! You've now got everything setup and are ready to start making contributions.

Finding something to work on

There are many ways in which you may contribute to the Sway project, some of which involve coding knowledge and some which do not. A few examples include:

  • Reporting bugs
  • Adding documentation to the Sway book
  • Adding new features or bugfixes for which there is already an open issue
  • Making feature requests

Check out our Help Wanted, Sway Book or Good First Issue issues to find a suitable task.

If you are planning something big, for example, related to multiple components or changes current behaviors, make sure to open an issue to discuss with us before starting on the implementation.

Contribution flow

This is a rough outline of what a contributor's workflow looks like:

  • Make sure what you want to contribute is already tracked as an issue.
    • We may discuss the problem and solution in the issue.
  • Create a Git branch from where you want to base your work. This is usually master.
  • Write code, add test cases, and commit your work.
  • Run tests and make sure all tests pass.
  • If the PR contains any breaking changes, add the breaking label to your PR.
  • Push your changes to a branch in your fork of the repository and submit a pull request.
    • Make sure mention the issue, which is created at step 1, in the commit message.
  • Your PR will be reviewed and some changes may be requested.
    • Once you've made changes, your PR must be re-reviewed and approved.
    • If the PR becomes out of date, you can use GitHub's 'update branch' button.
    • If there are conflicts, you can merge and resolve them locally. Then push to your PR branch. Any changes to the branch will require a re-review.
  • Our CI system (Github Actions) automatically tests all authorized pull requests.
  • Use Github to merge the PR once approved.

Thanks for your contributions!

Linking issues

Pull requests should be linked to at least one issue in the same repo.

If the pull request resolves the relevant issues, and you want GitHub to close these issues automatically after it merged into the default branch, you can use the syntax (KEYWORD #ISSUE-NUMBER) like this:

close #123

If the pull request links an issue but does not close it, you can use the keyword ref like this:

ref #456

Multiple issues should use full syntax for each issue and separate by a comma, like:

close #123, ref #456