The Sway Reference

This is the technical reference for the Sway programming language. For a prose explanation and introduction to the language, please refer to the Sway Book.

Installation

The Sway toolchain is required to compile Sway programs.

There are three ways to install the Sway toolchain:

The supported operating systems include Linux and macOS; however, Windows is unsupported.

Fuelup

Fuelup is the recommended tool for installation and management of the toolchain.

Cargo

Cargo may be used instead of Fuelup; however, the user needs to manage the toolchain themselves.

The advantage of using Cargo is the installation of plugins that have not been added into Fuelup.

The disadvantage occurs when Fuelup and Cargo are used in tandem because the latest plugins may not be recognized.

Source

The latest features may be accessed when installing from source; however, the features may not be ready for release and lead to unstable behavior.

Fuelup

Fuelup is a tool used to manage the Sway toolchain. It allows the user to download compiled binaries and switch between different versions of Sway.

The installation instructions can be found at the start of the Fuelup Book.

After installing fuelup run the following command to check the version:

fuelup --version

The output may look similar to:

fuelup 0.13.0

Cargo

Cargo can be used to install the Sway toolchain with various plugins.

Dependencies

A prerequisite for installing and using Sway is the Rust toolchain running on the stable channel.

After installing the Rust toolchain run the following command to check default channel:

rustup toolchain list

The output may look similar to:

stable-x86_64-unknown-linux-gnu (default)

Installation & Updating

The Sway toolchain can be installed/updated with:

cargo install forc

From Source

The Sway toolchain can be built directly from the Sway repository.

Installation & Updating

In the root of the repository /sway/<here> build forc with the following command:

cargo build

The executable binary can be found in /sway/target/debug/forc.

Using the Toolchain

After installing run the following command to check the version:

/sway/target/debug/forc --version

The output may look similar to:

forc 0.31.2

Fuel Core

The Fuel toolchain is an extension of the Sway toolchain.

It consists of a full node known as Fuel Core and it enables deployment and testing via the Rust SDK.

Installation & Updating

Fuel Core can be installed/updated with:

cargo install fuel-core

There may be additional system dependencies required for installation.

Sway Program Types

A Sway program is a file ending with the extension .sw, e.g. main.sw, and the first line of the file is a declaration of the type of program.

A Sway program can be one of four types:

  • contract
    • Primarily used for protocols or systems that operate within a fixed set of rules e.g. staking contracts, decentralized exchanges, etc.
  • library
    • Reusable code for handling common operations
  • script
    • Used for complex, multi-step, on-chain interactions that won't persist, such as using a decentralized exchange to create a leveraged position (borrow, swap, re-collateralize, borrow)
  • predicate
    • A set of preconditions to the construction of a transaction, the result of which must be a Boolean value of true in order for the transaction to be considered valid

Sway Project Types

A project type in Sway refers to which program type is in the main file of the project.

This means that there are four types of projects:

  • contracts
  • libraries
  • scripts
  • predicates

All four projects can contain multiple library files in the src directory.

There is a caveat when it comes to contracts, scripts and predicates and it's as follows:

  • A project can at most contain any one of a contract, script or predicate.

This means that a project cannot contain more than one contract, one script, one predicate and it cannot mix them together.

Entry Points

An entry point is the starting point of execution for a program.

Since a library is not directly deployable to the blockchain it does not have an entry point and instead its code is exported for use within other programs.

Unlike libraries; contracts, scripts and predicates all have an entry point. Contracts expose an Application Binary Interface (ABI) while scripts and predicates expose a main() function for entry.

Smart Contracts

A smart contract is a piece of bytecode that can be deployed to a blockchain via a transaction.

It can be called in the same way that an API may be called to perform computation and store and retrieve data from a database.

A smart contract consists of two parts:

Application Binary Interface (ABI)

The ABI is a structure which defines the endpoints that a contract exposes for calls. That is to say that functions defined in the ABI are considered to be external and thus a contract cannot call its own functions.

The following example demonstrates an interface for a wallet which is able to receive and send funds.

The structure begins by using the keyword abi followed by the name of the contract.

Inside the declaration are function signatures, annotations denoting the interaction with storage and documentation comments outlining the functionality.

library;

abi Wallet {
    /// When the BASE_ASSET is sent to this function the internal contract balance is incremented
    #[storage(read, write)]
    fn receive_funds();

    /// Sends `amount_to_send` of the BASE_ASSET to `recipient`
    ///
    /// # Arguments
    ///
    /// - `amount_to_send`: amount of BASE_ASSET to send
    /// - `recipient`: user to send the BASE_ASSET to
    ///
    /// # Reverts
    ///
    /// * When the caller is not the owner of the wallet
    /// * When the amount being sent is greater than the amount in the contract
    #[storage(read, write)]
    fn send_funds(amount_to_send: u64, recipient: Identity);
}

Implementation the ABI

Similar to traits in Rust implementing the ABI is done with the syntax impl <name-of-abi> for Contract.

All functions defined in the ABI must be declared in the implementation.

Since the interface is defined outside of the contract we must import it using the use syntax before we can use it.

contract;

use interface::Wallet;

impl Wallet for Contract {
    #[storage(read, write)]
    fn receive_funds() {
        // function implementation
    }

    #[storage(read, write)]
    fn send_funds(amount_to_send: u64, recipient: Identity) {
        // function implementation
    }
}

Library

A library is used to contain code that performs common operations in order to prevent code duplication.

Definition

Libraries are defined using the library keyword at the beginning of a file.

library;

Accessibility

Code defined inside a library, but more generally anywhere inside a Sway project, is considered to be private which means that it is inaccessible to other files unless explicitly exposed.

Code can be exposed through a two step process:

  • Add a pub keyword at the start of some code
  • Specify the library in the Forc.toml file as a dependency and then import the pub declaration
library;

// Cannot import because the `pub` keyword is missing
fn foo() {}

// Can import everything below because they are using the `pub` keyword
pub const ONE = __to_str_array("1");

pub struct MyStruct {}

impl MyStruct {
    pub fn my_function() {}
}

pub enum MyEnum {
    Variant: (),
}

pub fn bar() {}

pub trait MyTrait {
    fn my_function();
}

The following structures can be marked as pub:

  • Globally defined constants
  • Structs
  • Enums
  • Functions
  • Traits

Deployment

Libraries cannot be directly deployed to a blockchain, but they can be deployed as part of a contract.

Internal Libraries

A library is internal to a project if it is in the same source src directory as the other program files.

$ tree
.
├── Cargo.toml
├── Forc.toml
└── src
    ├── lib.sw
    └── my_library.sw

To be able to use our library my_library.sw in lib.sw there are two steps to take:

  1. Bring our library into scope by using the mod keyword followed by the library name
  2. Use the use keyword to selectively import various items from the library
library;

mod my_library;

use my_library::bar;

// `bar` from `my_library` is now available throughout the file

External Libraries

An external library is a library that is outside of the src directory (most likely in an entirely different project).

$ tree
.
├── my_library
│   ├── Cargo.toml
│   ├── Forc.toml
│   └─── src
│       └── lib.sw
│
└── my_other_library
    ├── Cargo.toml
    ├── Forc.toml
    └─── src
        └── lib.sw

Libraries

my_other_library

my_other_library has a function quix() which can be imported into my_library because it uses the pub keyword.

library;

pub fn quix() {}

my_library

To be able to use quix() inside my_library there are two steps to take.

Add to Dependencies

Add my_other_library as a dependency under the [dependencies] section of the my_library Forc.toml file.

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

[dependencies]
my_other_library = { path = "../my_other_library" }
std = { path = "../../../../../../../../../sway-lib-std" }

Import

Use the use keyword to selectively import our code from my_other_library

library;

use my_other_library::quix;

// `quix` from `my_other_library` is now available throughout the file

Scripts

A script is an executable that does not need to be deployed because it only exists during a transaction.

It can be used to replicate the functionality of contracts, such as routers, without the cost of deployment or increase of the blockchain size.

Some properties of a script include:

  • It cannot be called by a contract
  • It is stateless but can interact with storage through a contract
  • Can call multiple contracts

Example

The following example demonstrates a script which takes one argument and returns the Boolean value of true.

script;

// All scripts require a main function. The return type is optional.
fn main(amount: u64) -> bool {
    true
}

Predicates

A predicate is an executable that represents a UTXO spending condition, such as a multisig predicate, which has restrictions on the VM instructions that can be used .

It does not need to be deployed to a blockchain because it only exists during a transaction. That being said, the predicate root is on chain as the owner of one or more UTXOs.

Predicates can neither read from nor write to any contract state. Moreover, they cannot use any contract instructions.

Transfer Coins to a Predicate

In Fuel, coins can be sent to an address uniquely representing a particular predicate's bytecode (the bytecode root, calculated here).

Spending Predicate Coins

The coin UTXOs become spendable not on the provision of a valid signature, but rather if the supplied predicate both has a root that matches their owner, and evaluates to true.

If a predicate reverts, or tries to access impure VM opcodes, the evaluation is automatically false.

Spending Conditions

Predicates may introspect the transaction spending their coins (inputs, outputs, script bytecode, etc.) and may take runtime arguments (the predicateData), either or both of which may affect the evaluation of the predicate.

Example

Similar to a script, a predicate consists of a single main() function which can take any number of arguments but must return a Boolean. In order for the predicate to be valid, the returned Boolean value must be true.

predicate;

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

Built-in Types

Sway is a statically typed language therefore every value must be known at compile time. This means that each value must have a type and the compiler can usually infer the type without the user being required to specify it.

Sway provides a number of out-of-the-box (primitive) types which can be used to construct more complex data structures and programs.

Primitive Types

Sway has the following primitive types:

  1. Numerics
    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. u256 (256-bit unsigned integer)
    6. hexadecimal, binary & base-10 syntax
  2. Boolean
    1. bool (true or false)
  3. Strings
    1. str (string slice)
    2. str[n] (fixed-length string of size n)
  4. Bytes
    1. b256 (256 bits / 32 bytes, i.e. a hash)

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

All other types in Sway are built up of these primitive types, or references to these primitive types.

Compound Types

Compound types are types that group multiple values into one type.

Sway has the following compound types:

  1. Arrays
  2. Tuples
  3. Structs
  4. Enums

Numeric Types

Broadly speaking there are two types of integers:

Signed Integers

A signed integer is a whole number which can take the value of zero and both negative and positive values. This means that a signed integer can take values such as:

  • -42
  • 0
  • 42

In order to achieve this one bit must be kept for tracking the sign (+ or -) of the value and thus the range of available values is smaller than an unsigned integer.

For those inclined, the range for an n-bit signed integer is -2n-1 to 2n-1-1.

Sway does not natively support signed integers however there is nothing stopping a library from using primitives to create types that act like signed types.

Unsigned Integers

An unsigned integer is a whole number which can take the value of zero or any positive number, but cannot be negative. This allows for one more bit of values to be used for the positive numbers and thus the positive range is significantly larger than for signed integers.

An example of available values is:

  • 0
  • 42

For those inclined, the range for an n-bit unsigned integer is 0 to 2n-1.

Alternative Syntax

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 hexadecimal = 0xffffff;
    let binary = 0b10101010;
    let base_10 = 10;
    let underscore_delineated_base_10 = 100_000;
    let underscore_delineated_binary = 0x1111_0000;
    let underscore_delineated_hexadecimal = 0xfff_aaa;

Boolean Type

A Boolean is a type that is represented by either a value of one or a value of zero. To make it easier to use the values have been given names: true & false.

Boolean values are typically used for conditional logic or validation, for example in if expressions, and thus expressions are said to be evaluated to true or false.

Using the unary operator ! the Boolean value can be changed:

  • From true to false
  • From false to true

Example

The following example creates two Boolean variables, performs a comparison using the unary operator ! and implicitly returns the result.

fn returns_true() -> bool {
    let is_true = true;
    let is_false = false;

    // implicitly returns the Boolean value of `true`
    is_true == !is_false
}

String Type

A string is a collection of characters (letters, numbers etc.).

Sway has one string type and it's a fixed length string which has the following implications:

  • A string cannot be grown or shrunk during execution
  • The content of the string must meet its length
    • This could be via a legitimate value that takes up the entire length or through padding

The reason for this is that the compiler must know the size of the type and the length is a part of the type.

A string can be created through the use of double-quotation marks " around the text. The length of the string is permanently set at that point and cannot be changed even if the variable is marked as mutable.

    // The variable `fuel` is a string slice with length equals 4
    let fuel = "fuel";
    let crypto = __to_str_array("crypto");

Strings default to UTF-8 in Sway.

Bytes

Sway has a single "bytes" type which is the b256.

As the name suggests it contains 256 bits / 32 bytes of information. Unlike some other programming languages this type is treated as a single, whole, type unlike an array of bytes which is iterated over.

    let zero = 0x0000000000000000000000000000000000000000000000000000000000000000;

Tuples

A tuple is a general-purpose static-length aggregation of types, in other words, it's 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 arity, define the tuple's type.

Declare

To declare a tuple we wrap the values in ().

    // Define a tuple containing 2 u64 types
    let mut balances = (42, 1337);

Retrieve by Index

Values can be retrieved individually from the tuple by specifying the index.

    // first = 42, second = 1337
    let first = balances.0;
    let second = balances.1;

Mutate

A value can be mutated in a tuple as long as the tuple is declared to be mutable and the new value has the same type as the previous value.

    // 12 has the same type as 42 (u64) therefore this is valid
    balances.0 = 12;

    // true is a Boolean and the tuple expects a u64 therefore this is invalid
    // balances.0 = true;

The entire tuple can be overwritten when it is mutable and the type for each value is the same.

    // 3 is the same type as 42 (u64) and so is 4 and 1337 therefore this is valid
    balances = (3, 4);

Destructure

Elements can be destructured from a tuple into individual variables.

    // first = 42, second = 1337
    let (first, second) = balances;

We can also ignore elements when destructuring.

    // 42 is ignored and cannot be used
    let (_, second) = balances;

Arrays

An array is similar to a tuple, but an array's values must all be of the same type. It's defined using square brackets [] and separates its values using commas.

Unlike a tuple, an array can be iterated over through indexing.

fn syntax() {
    let array = [1, 2, 3, 4, 5];

    let mut counter = 0;
    let mut total = 0;

    while counter < 5 {
        total += array[counter];
        counter += 1;
    }
}

Arrays are allocated on the stack and thus the size of an array is considered to be static. What this means is that once an array is declared to have a size of n it cannot be changed to contain more, or fewer, elements than n.

Structs

A struct in Sway is a product type which is a data structure that allows grouping of various types under a name that can be referenced, unlike a tuple. The types contained in the struct are named and thus they can be referenced by their names as well.

Declaration

The following syntax demonstrates the declaration of a struct named Foo containing two fields - public field bar, a u64, and a private field baz, a bool.

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

Public fields are accessible in all the modules in which the struct is accessible. Private fields are accessible only within the module in which the struct is declared.

Instantiation

To instantiate a struct the name of the struct must be used followed by {} where the fields from the declaration must be specified inside the brackets. Instantiation requires all fields to be initialized, both private and public.

fn hardcoded_instantiation() {
    // Instantiate the variable `foo` as `Foo`
    let mut foo = Foo {
        bar: 42,
        baz: false,
    };

    // Access and write to "baz"
    foo.baz = true;
}

fn variable_instantiation() {
    // Declare variables and pass them into `Foo`
    let number = 42;
    let boolean = false;

    let mut foo = Foo {
        bar: number,
        baz: boolean,
    };

    // Access and write to "baz"
    foo.baz = true;
}

fn shorthand_instantiation() {
    // Declare variables with the same names as the fields in `Foo`
    let bar = 42;
    let baz = false;

    // Instantiate `foo` as `Foo`
    let mut foo = Foo { bar, baz };

    // Access and write to "baz"
    foo.baz = true;
}

Structs with private fields can be instantiated only within the module in which the struct is declared.

Destructuring

The fields of a struct can be accessed through destructuring.

fn destructuring() {
    let foo = Foo {
        bar: 42,
        baz: false,
    };

    // bar and baz are now accessible as variables
    let Foo { bar, baz } = foo;

    if baz {
        let quix = bar * 2;
    }

    // You may use `..` to omit the remaining fields if the types match
    // The compiler will fill them in for you
    let Foo { bar, .. } = foo;
}

When destructuring structs with private fields outside of a module in which the struct is defined, the private fields must be omitted by using the ...

Enums

An enum, also known as a sum type, is a type that consists of several variants where each variant is named and has a type.

Let's take a look at an example where we define an enum called Color with a few color variations.

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

We begin by using the enum keyword followed by the name for our enumeration. The variants are contained inside {} and they are ordered sequentially from top to bottom. Each variant has a name, such as the first Blue variant, and a type, which in this case is the unit type () for all variants.

The unit type is a type that does not contain any data however any type can be used.

    // To instantiate an enum with a variant of the unit type the syntax is
    let blue = Color::Blue;
    let silver = Color::Silver;

Enums of Structs

In order to demonstrate more complex data types we can define a struct and assign that struct as a data type for any of an enum's variants.

Here we have a struct Item and an enum MyEnum. The enum has one variant by the name Product and its type is declared to the right of : which in this case is our struct Item.

struct Item {
    amount: u64,
    id: u64,
    price: u64,
}

enum MyEnum {
    Product: Item,
}

fn main() {
    let my_enum = MyEnum::Product(Item {
        amount: 2,
        id: 42,
        price: 5,
    });
}

Enums of Enums

Similar to structs we can use other enums as types for our variants.

enum UserError {
    InsufficientPermissions: (),
    Unauthorized: (),
}

enum Error {
    UserError: UserError,
}

fn main() {
    let my_enum = Error::UserError(UserError::Unauthorized);
}

Variables

A variable is a way to reference some information by a specific name and it can take the form of a variety of data structures.

In Sway there are two states that a variable can take:

  • Immutable
    • Can be read but cannot be changed after it has been declared
  • Mutable
    • Can be read and can have its value changed if and only if the new value is the same type

By default all variables in Sway are immutable unless declared as mutable through the use of the mut keyword. This is one of the ways in which Sway encourages safe programming, and many modern languages have the same default.

In the following sections, we'll take a look at two keywords that are used to instantiate information (let & const) and a way to temporarily reuse a variable name without affecting the original instantiation through variable shadowing.

let

The let keyword is used to assign a value to a variable during run-time. It can only be used inside of a function and its value can be changed when declared as mutable.

Immutable

We can declare a variable that cannot have its value changed in the following way.

    let foo = 5;

By default foo is an immutable u64 with the value of 5. This means that we can pass foo around and its value can be read, but it cannot have its value changed from 5 to any other value.

Mutable

We can declare a variable that can have its value changed through the use of the mut keyword.

    let mut foo = 5;
    foo = 6;

Constants

Constants are similar to immutable let variables; however, there are a few differences:

  • Constants are always evaluated at compile-time.
  • Constants can be declared both inside of a function and at global/impl scope.
  • The mut keyword cannot be used with constants.

Declaration

To define a constant the const keyword is used followed by a name and an assignment of a value.

    const FOO = 5;

The example above hardcodes the value of 5 however function calls may also be used alongside built-in types.

impl self Constants

Constants can also be declared inside impl blocks. In this case, the constant is referred to as an associated constant.

struct Point {
    x: u64,
    y: u64,
}

impl Point {
    const ZERO: Point = Point { x: 0, y: 0 };
}

fn main() -> u64  {
    Point::ZERO.x
}

Shadowing

When looking at the let variable we've seen that the value can be changed through the use of the mut keyword. We can take this a couple steps further through reassignment and variable shadowing. Note that shadowing applies only to variables. Constants cannot be shadowed.

Reassignment

We can redefine the type and value of a variable by instantiating a new version after the first declaration.

    // Set `foo` to take the value of `5` and the default `u64` type
    let foo = 5;

    // Reassign `foo` to be a `str` with the value of `Fuel`
    let foo = "Fuel";

Variable Shadowing

If we do not want to alter the original variable but we'd like to temporarily reuse the variable name then we can use block scope to constrain the variable.

    let foo = 5;
     {
        let foo = 42;
    }
    assert(foo == 5);

foo is defined inside the curly brackets { } and only exist inside the { .. } scope; therefore, the original foo variable with the value of 5 maintains its value.

Comments

There are two kinds of comments in Sway.

Regular Comments

Regular comments are broken down into two forms of syntax:

  • // comment
  • /* comment */

The first form starts after the two forward slashes and continues to the end of the line.

Comments can be placed on multiple lines by starting each line with // and they can be placed at the end of some code.

    // imagine that this line is twice as long
    // and it needed to be split onto multiple lines
    let baz = 8; // Eight is a good number

Similarly, the second form continues to the end of the line and it can also be placed at the end of some code.

    /*
        imagine that this line is twice as long
        and it needed to be split onto multiple lines
    */

Documentation Comments

Documentation comments start with three forward slashes /// and are placed on top of functions or above fields e.g. in a struct.

Documentation comments are typically used by tools for automatic documentation generation.

/// Data structure containing metadata about product XYZ
struct Product {
    /// Some information about field 1
    field1: u64,
    /// Some information about field 2
    field2: bool,
}

/// Creates a new instance of a Product
///
/// # Arguments
///
/// - `field1`: description of field1
/// - `field2`: description of field2
///
/// # Returns
///
/// A struct containing metadata about a Product
fn create_product(field1: u64, field2: bool) -> Product {
    Product { field1, field2 }
}

Functions, methods, and associated functions

Functions, and by extension methods and associated functions, are a way to group functionality together in a way that allows for code reuse without having to re-write the code in each place that it is used.

The distinction between a function, method and associated function is as follows:

  • A function is a grouping of code that is independent of any object
  • A method is a function that is associated with an object and it uses self as the first parameter
  • An associated function is a method but without the self parameter

Function Declaration

A function declaration consists of a few components

  • The fn keyword
  • A unique name for the function
  • Comma separated optional parameters, and their types, inside ()
  • An optional return type

Here is a template that applies to the aforementioned functions.

fn my_function(my_parameter: u64 /* ... */ ) -> u64 {
    // function code
    42
}

Functions

In this section we will define a function that takes two numerical inputs and returns a Boolean value indicating whether they are equal. We will also take a look at how to use the function.

Declaration

The following function is called equals and it takes two parameters of type u64 (64-bit unsigned integers). It performs a comparison and implicitly returns the result of that comparison.

fn equals(first_parameter: u64, second_parameter: u64) -> bool {
    first_parameter == second_parameter
}

Usage

The following is a way to use the function defined above.

    let result_one = equals(5, 5); // evaluates to `true`
    let result_two = equals(5, 6); // evaluates to `false`

Methods

Methods are defined within the context of a struct (or enum) and either refer to the type or mutate it.

The first parameter of a method is always self, which represents the instance of the type the method is being called on.

Declaration

In this example we will take a look at a struct however an enum will work in the same way.

struct Foo {
    bar: u64,
}

We start by using the impl (implementation) keyword, followed by the name of our struct, to define a function that belongs to our object i.e. a method.

impl Foo {
    // refer to `bar`
    fn add_number(self, number: u64) -> u64 {
        self.bar + number
    }

    // mutate `bar`
    fn increment(ref mut self, number: u64) {
        self.bar += number;
    }
}

Usage

To call a method use the dot syntax: <variable name>.<method name>().

    let mut foo = Foo { bar: 42 };
    let result = foo.add_number(5); // evaluates to `47`
    foo.increment(5); // `bar` inside `foo` has been changed from 42 to 47

Associated Functions

Associated functions are similar to methods in that they are also defined in the context of a struct or enum, but they do not 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.

Constructors

A distinguished family of associated functions of a specific type are type constructors. Constructors are associated functions that construct, or in other words instantiate, new instances of a type. Their return type always includes the type itself, and is often just the type itself.

Public structs that have private fields must provide a public constructor, or otherwise cannot be instantiated outside of the module in which they are declared.

Declaration

In this example we will take a look at a struct; however, an enum will work in the same way.

struct Foo {
    bar: u64,
}

We start by using the impl (implementation) keyword, followed by the name of our struct, to define a function that belongs to our object i.e. a method.

impl Foo {
    // this is an associated function because it does not take `self` as a parameter
    // it is also a constructor because it instantiates
    // and returns a new instance of `Foo`
    fn new(number: u64) -> Self {
        Self { bar: number }
    }
}

Usage

To call an associated function on a type we use the following syntax.

    let foo = Foo::new(42);

Returning from functions

In the previous sections we have seen how functions return values without going into detail. In this section we will take a closer look at how we can return data from a function.

There are two ways to return:

When returning data from a function the return types must match up with the return types declared in the function signature. This means that if the first return type is a u64 then the type of the first value being returned must also be a u64.

Explicit Return

To return from a function explicitly we use the return keyword followed by the arguments and a semi-colon.

fn main() -> bool {
    return true;
}

A return expression is typically used at the end of a function; however, it can be used earlier as a mechanism to exit a function early if some condition is met.

fn return_data(parameter_one: u64, parameter_two: bool) -> (bool, u64) {
    if parameter_two {
        return (!parameter_two, parameter_one + 42);
    }
    return (parameter_two, 42);
}

Implicit Return

To return from a function implicitly we do not use the return keyword and we omit the semi-colon at the end of the line.

fn main() -> bool {
    true
}

An implicit return is a special case of the explicit return. It can only be used at the end of a function.

fn return_data(parameter_one: u64, parameter_two: bool) -> (bool, u64) {
    if parameter_two {
        (!parameter_two, parameter_one + 42)
    } else {
        (parameter_two, 42)
    }
}

Control Flow

A control flow in a program is the order in which instructions are executed.

For example, a function may take an input u64 and if the value is greater than 5 then it calls one function otherwise it calls a different function.

Controlling the order of instructions can be done through the use of conditional expressions such as if and match and through looping.

if expressions

Sway supports if, else, and else if expressions which provide control over which instructions should be executed depending on the conditions.

Conditional Branching

In the following example we have a hardcoded variable number set to the value of 5 which is put through some conditional checks.

    let number = 5;

    if number % 3 == 0 {
        // call function 1
    } else if number % 4 == 0 {
        // call function 2
    } else {
        // call function 3
    }

    // more code here

The conditional checks are performed in the order that they are defined therefore the first check is to see if the number is divisible by 3.

If the condition evaluates to the Boolean value of true then we call function 1 and we move on to the end where the comment more code here is written. We do not evaluate the remaining conditions.

On the other hand if the condition evaluates to false then we check the next condition, in this case if the number is divisible by 4. We can have as many else if checks as we like as long as they evaluate to a Boolean.

At the end there is a special case which is known as a catch all case i.e. the else. What this means is that we have gone through all of our conditional checks above and none of them have been met. In this scenario we may want to have some special logic to handle a generic case which encompases all the other conditions which we do not care about or can be treated in the same way.

Using if & let together

In Conditional Branching we have opted to call some functions depending on which condition is met however that is not the only thing that we can do. Since if's are expressions in Sway we can use them to match on a pattern.

if let

In the following examples we combine if and let into if let followed by some comparison which must evaluate to a Boolean.

enum Foo {
    One: (),
    Two: (),
}

Example 1

Here we check to see if the hardcoded variable one is the same as the first variant of Foo.

    let one = Foo::One;
    let mut result = 0;
    
    if let Foo::One = one {
        result = 1;
    }

Example 2

Alternatively, we can take the outcome of the comparison and assign it directly to a variable.

    let one = Foo::One;
    let result = if let Foo::One = one {
        1
    } else {
        2
    };

The syntax above can be altered to include an else if.

match

If expressions can be used to check a large number of conditions however, there is an alternative syntax which allows us to perform advanced pattern matching.

A match expression matches on a variable and checks each case, also known as an arm, to see which branch of logic should be performed.

The cases are checked sequentially in the order they are declared, i.e. from top to bottom, and the last arm must ensure that all cases in the pattern are covered otherwise the compiler will not know how to handle an unspecified pattern and will throw an error.

In the following sections we'll look at:

  • A primitive case where a single line of code is used in a case
  • Expand the first example to use code blocks in the multi line case
  • Look at complex pattern matching to demonstrate their utility

Single Line Arm

The following example demonstrates how a type can be matched on and its output is assigned to a variable. The assignment to a variable is optional.

    let number = 5;

    let result = match number {
        0 => 10,
        1 => 20,
        5 => 50,
        6 | 7 => 60,
        catch_all => 0,
    };

The left side of the arrow => is the pattern that we are matching on and the right side of the arrow => is the logic that we want to perform, in this case we are returning a different multiple of 10 depending on which arm is matched.

We check each arm starting from 0 and make our way down until we either find a match on our pattern or we reach the catch_all case.

The | operator can be used to produce a pattern that is a disjuction of other patterns.

The catch_all case is equivalent to an else in if expressions and it does not have to be called catch_all. Any pattern declared after a catch_all case will not be matched because once the compiler sees the first catch_all it stop performing further checks.

Multi Line Arm

The arm of a match expression can contain multiple lines of code by wrapping the right side of the arrow => in brackets {}.

    let number = 5;

    let result = match number {
        0 => {
            // Multiple lines of code here then return 10
            10
        },
        1 => 20,
        5 => 50,
        catch_all => 0,
    };

Complex Patterns

Match expressions are meant to cover advanced patterns so the following sections demonstrate some examples:

Enums

An enum can be matched on by specifying the name of the enum and the variant.

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

fn enum_match(input: Color) {
    let result = match input {
        Color::Red => 0,
        Color::Green => 1,
        Color::Blue => 2,
    };
}

Structs

We can match on specific arguments inside a struct while ignoring the rest by using ...

struct Point {
    x: u64,
    y: u64
}

fn struct_matching() {
    let point = Point {
        x: 1u64,
        y: 2u64,
    };

    let result = match point {
        Point { x: 5, y } => y + 1,
        Point { x, .. } => x,
        Point { y, .. } => y,
        _ => 42,
    };
}

If the struct is imported from another module and has private fields, the private fields must always be ignored by using ...

Constants

Variables can be matched on but only if they are constants.

const NUMBER_1: u64 = 7;
const NUMBER_2: u64 = 14;

fn constant_match() {
    let number = 5;

    let result = match number {
        NUMBER_1 => 1,
        NUMBER_2 => 42,
        other => other,
    };
}

Nested Expressions

We can nest match expressions by placing them inside code blocks.

enum TopLevel {
    One: (),
    Two: SecondLevel,
}

enum SecondLevel {
    Value1: u64,
    Value2: (),
}

fn nested_match(input: TopLevel) -> u64 {
    match input {
        TopLevel::One => 1,
        TopLevel::Two(second) => {
            match second {
                SecondLevel::Value1(2) => 2,
                SecondLevel::Value1(_) => 3,
                SecondLevel::Value2 => 42,
            }
        },
    }
}

Multiple Values

We can match on multiple values by wrapping them in a tuple and then specifying each variant in the same structure (tuple) that they have been defined.

use core::ops::Eq;

enum Binary {
    True: (),
    False: (),
}

impl Eq for Binary {
    fn eq(self, other: Self) -> bool {
        match (self, other) {
            (Binary::True, Binary::True) => true,
            (Binary::False, Binary::False) => true,
            _ => false,
        }
    }
}

Looping

A loop is a type of operation which allows us to perform computation a certain number of times. For example, given a collection of items we could call a method on the first item and iterate until the method has been called on each item.

Usually, a loop has a condition which prevents it from continuing indefinitely however it is possible to create a loop that never stops i.e. an infinite loop.

Programming languages have various forms of syntax for declaring a loop which may slightly alter how the iteration takes place.

Sway has the following loops:

while

A while loop uses the while keyword followed by a condition which evaluates to a Boolean.

    let mut counter = 0;
    let mut condition = true;
    while counter < 10 && condition {
        counter += 1;
        if 5 < counter {
            condition = false;
        }
    }

In the example above we use two conditions.

  1. If the counter is less than 10 then continue to iterate
  2. If the condition variable is true then continue to iterate

As long as both those conditions are true then the loop will iterate. In this case the loop will finish iterating once counter reaches the value of 6 because condition will be set to false.

Nested loops

Sway also allows nested while loops.

    while true {
        // computation here
        while true {
            // more computation here
        }
    }

break

break is a keyword available for use inside of a while loop and it is used to exit out of the loop before the looping condition is met.

    let mut counter = 0;
    while counter < 10 {
        counter += 1;
        if 5 < counter {
            break;
        }
    }

In the example above the while loop is set to iterate until counter reaches the value of 10 however the if expression will break out of the loop once counter reaches the value of 6.

continue

continue is a keyword available for use inside of a while loop and it is used to skip to the next iteration without executing the code after continue.

    let mut counter = 0;
    while counter < 10 {
        counter += 1;
        if counter % 2 == 0 {
            continue;
        }
        // "other code"
    }

In the example above the while loop is set to iterate until counter reaches the value of 10 however the if expression will skip (not execute) the "other code" when counter is an even value. For example, this could be used to add all the odd numbers from 0 to 10.

Annotations

Types

Sway is a compiled language and as such each data structure has a definition i.e. a type which has some size that must be allocated on the stack.

The compiler can usually infer the type based on its usage however there may be occasions where the compiler cannot make the inference or the developer may deem it more useful to explicitly annotate a variable in order to make the code easier to read.

Annotating a variable is done by placing the annotation after the variable name but before the assignment (the = sign).

    let bar: str = "sway";
    let baz: bool = true;

The compiler will disallow incorrect type annotations therefore replacing the bool annotation on the variable baz with a u64 will result in a compilation error.

Attributes

An attribute is a metadatum which provides some additional functionality.

Storage

A storage attribute indicates the purity of a function i.e. whether it:

  • reads from storage
  • writes to storage
  • reads from and writes to storage
  • does not read or write (is pure)

When a function is pure the annotation is omitted otherwise the correct annotation must be placed above the function signature.

More information about storage can be found in the common storage operations section.

Reading from Storage

When we read from storage we use the read keyword.

#[storage(read)]

Writing to Storage

When we write to storage we use the write keyword.

#[storage(write)]

Reading & Writing

When we read from and write to storage we use the read & write keywords.

#[storage(read, write)]

Payable

The payable annotation is used to allow a contract function to accept an asset forwarded via a call.

Usage

To allow a contract to accept assets we use the payable keyword.

    #[payable]
    fn deposit();

Test

Sway provides the #[test] attribute which enables unit tests to be written in Sway.

Success case

The #[test] attribute indicates that a test has passed if it did not revert.

#[test]
fn equal() {
    assert_eq(1 + 1, 2);
}

Revert Case

To test a case where code should revert we can use the #[test(should_revert)] annotation. If the test reverts then it will be reported as a passing test.

#[test(should_revert)]
fn unequal() {
    assert_eq(1 + 1, 3);
}

We may specify a code to specifically test against.

#[test(should_revert = "18446744073709486084")]
fn assert_revert_code() {
    assert(1 + 1 == 3);
}

#[test(should_revert = "42")]
fn custom_revert_code() {
    revert(42);
}

Allow

Dead code

The #[allow(dead_code)] annotation disables warnings which are emitted by the compiler for code that is unused.

#[allow(dead_code)]
fn unused_function() {}

Deprecated

The #[allow(deprecated)] annotation disables warnings which are emitted by the compiler for usage of deprecated items.

#[deprecated(note = "this is deprecated")]
struct DeprecatedStruct {}

#[allow(deprecated)]
fn using_deprecated_struct() {
    let _ = DeprecatedStruct {};
}

Inline

When making a call the compiler may generate code to call a function where it is defined or it may copy the function code (inline) to prevent additional code generation.

The Sway compiler automatically inlines functions based on internal heuristics; however, the inline attribute may be used to suggest, but not require, code generation or code copying.

Generate code

To suggest code generation use the never keyword.

#[inline(never)]
fn foo() {}

Copy code

To suggest code copy use the always keyword.

#[inline(always)]
fn bar() {}

Deprecated

This annotation marks an item as deprecated, which makes the compiler to emit a warning for each usage of the item. This warning can be disabled using #[allow(deprecated)].

It is also possible to customize the warning message using the argument note.

#[deprecated(note = "this is deprecated")]
struct DeprecatedStruct {}

#[allow(deprecated)]
fn using_deprecated_struct() {
    let _ = DeprecatedStruct {};
}

Traits

A trait describes an abstract interface that types can implement. This interface consists of an interface surface of associated items, along with methods.

trait Trait {
    fn fn_sig(self, b: Self) -> bool;
} {
    fn method(self, b: Self) -> bool {
        true
    }
}

Associated items come in two varieties:

All traits define an implicit type parameter Self that refers to "the type that is implementing this interface". Traits may also contain additional type parameters. These type parameters, including Self, may be constrained by other traits and so forth as usual.

Traits are implemented for specific types through separate implementations.

Associated functions

Trait functions consist of just a function signature. This indicates that the implementation must define the function.

Associated constants

Associated constants are constants associated with a type.

An associated constant declaration declares a signature for associated constant definitions. It is written as const, then an identifier, then :, then a type, finished by a ;.

The identifier is the name of the constant used in the path. The type is the type that the definition has to implement.

An associated constant definition defines a constant associated with a type.

Associated constants examples

script;

trait T {
    const C: bool;
}

struct S {}

impl T for S {
    const C: bool = true;
}

fn main() -> bool {
    let s = S {};
    S::C
}

Associated constants may omit the equals sign and expression to indicate implementations must define the constant value.

Associated types

Associated types in Sway allow you to define placeholder types within a trait, which can be customized by concrete implementations of that trait. These associated types are used to specify the return types of trait methods or to define type relationships within the trait.

Associated types examples

script;

trait TypeTrait {
    type T;

    fn method(self, s1: Self::T) -> Self::T;
}

struct Struct {}

struct Struct2 {}

impl TypeTrait for Struct2 {
  type T = Struct;

  fn method(self, s1: Self::T) -> Self::T {
    s1
  }
}

fn main() -> u32 {
  Struct2{}.method(Struct{});

  1
}

Generics

Style Guide

Programming languages have different ways of styling code i.e. how variables, functions, structures etc. are written.

The following snippets present the style guide for writing Sway.

TODO: overview of content

Naming Convention

A naming convention is a set of rules used to standardize how code is written.

CapitalCase

Structs, traits, and enums are CapitalCase which means each word has a capitalized first letter. The fields inside a struct should be snake_case and CapitalCase inside an enum.

struct MultiSignatureWallet {
    owner_count: u64,
}

trait MetaData {
    // code
}

enum DepositError {
    IncorrectAmount: (),
    IncorrectAsset: (),
}

snake_case

Modules, variables, and functions are snake_case which means that each word is lowercase and separated by an underscore.

Module name:

library;

Function and variable:

fn authorize_user(user: Identity) {
    let blacklist_user = false;
    // code
}

SCREAMING_SNAKE_CASE

Constants are SCREAMING_SNAKE_CASE which means that each word in capitalized and separated by an underscore.

const MAXIMUM_DEPOSIT = 10;

Type Annotations

When declaring a variable it is possible to annotate it with a type; however, the compiler can usually infer that information automatically.

The general approach is to omit a type if the compiler does not throw an error; however, if it is deemed clearer by the developer to indicate the type then that is also encouraged.

fn execute() {
    // Avoid unless it's more helpful to annotate
    let executed: bool = false;

    // Generally encouraged
    let executed = false;
}

Struct Shorthand

A struct has a shorthand notation for initializing its fields. The shorthand works by passing a variable into a struct with the exact same name and type.

The following struct has a field amount with type u64.

struct Structure {
    amount: u64,
}

Using the shorthand notation we can initialize the struct in the following way.

fn call(amount: u64) {
    let structure = Structure { amount };
}

The shorthand is encouraged because it is a cleaner alternative to the following.

fn action(value: u64) {
    let amount = value;
    let structure = Structure { amount: value };
    let structure = Structure { amount: amount };
}

Enums

An enum may contain many types including other enums.

pub enum Error {
    StateError: StateError,
    UserError: UserError,
}

pub enum StateError {
    Void: (),
    Pending: (),
    Completed: (),
}

pub enum UserError {
    InsufficientPermissions: (),
    Unauthorized: (),
}

Encouraged

The preferred way to use enums is to use the individual (not nested) enums directly because they are easy to follow and the lines are short:

    let error1 = StateError::Void;
    let error2 = UserError::Unauthorized;

Discouraged

If you wish to use the nested form of enums via the Error enum from the example above, then you can instantiate them into variables using the following syntax:

    let error1 = Error::StateError(StateError::Void);
    let error2 = Error::UserError(UserError::Unauthorized);

Key points to note:

  • You must import all of the enums i.e. Error, StateError & UserError
  • The lines may get unnecessarily long (depending on the names)
  • The syntax is unergonomic

Returning

In returning from functions we outline two styles of returning:

In general the preferred style is to follow the implicit return however both are perfectly acceptable.

Pattern Matching

The following examples present pattern matching using the match keyword for the catch-all case.

Encouraged

The _ is used for the catch-all to indicate the important cases have been defined above and the last case is not important enough to warrant a name.

fn unnamed_case(shape: Shape) {
    let value = match shape {
        Shape::Triangle => 3,
        Shape::Quadrilateral => 4,
        Shape::Pentagon => 5,
        _ => 0,
    };
}

Alternative

We may apply an appropriate name to provide context to the reader; however, unless it provides additional information the preferred usage is defined in the encouraged case.

fn named_case(shape: Shape) {
    let value = match shape {
        Shape::Triangle => 3,
        Shape::Quadrilateral => 4,
        Shape::Pentagon => 5,
        _invalid_shape => 0,
    };
}

Comments

In regular comments we outline two forms:

  • // comment
  • /* comment */

The first form is generally encouraged however there may be instances where a comment needs to be placed in the middle of some code in which case the second form is encouraged.

For example, in function declaration the second form is used to indicate additional parameters.

Getter Functions

Functions that return values typically follow one of two styles:

  • Prepending get_ to the start of the name
  • Omitting get_

Encouraged

In Sway the encouraged usage is to omit the get_ prefix.

fn maximum_deposit() -> u64 {
    100
}

Discouraged

That is to say the following is discouraged.

fn get_maximum_deposit() -> u64 {
    100
}

Unused Variables

A good practice is naming variables appropriately; however, variables may be unused at times such as the timestamp from the call().

fn unused_variable() -> u64 {
    let (timestamp, deposit_amount) = call();

    deposit_amount
}

Named

We may preserve the name to provide context to the reader by prepending the variable with _.

fn named_unused_variable() -> u64 {
    let (_timestamp, deposit_amount) = call();

    deposit_amount
}

Nameless

We may discard the context and the value by assigning it to _.

fn nameless_variable() -> u64 {
    let (_, deposit_amount) = call();

    deposit_amount
}

Intermediate Variables

An intermediate variable, or a temporary variable, is a variable that is typically used once. In most cases we avoid creating intermediate variables; however, there are cases where they may enrich the code.

Contextual Assignment

It may be beneficial to use an intermediate variable to provide context to the reader about the value.

fn contextual_assignment() {
    let remaining_amount = update_state();
    // code that uses `remaining_amount` instead of directly calling `update_state()`
}

Shortened Name

In the cases of multiple levels of indentation or overly verbose names it may be beneficial to create an intermediate variable with a shorter name.

fn shortened_name() {
    let remaining_amount = update_state_of_vault_v3_storage_contract();
    // code that uses `remaining_amount` instead of directly calling `update_state_of_vault_v3_storage_contract()`
}

Storage

A smart contract is able to perform computation and store & manipulate data over time.

In the following sections we'll take a look at how Sway handles storage through:

Initialization

Storage is declared through the use of the storage keyword.

Inside the storage block each variable is named, associated with a type and a default value.

storage {
    // variable_name1: variable_type1 = default_value1,
    // variable_name2: variable_type2 = default_value2,
    // ...
}

Example

In the following example we will take a look at two ways of storing a struct.

  • Explicitly declaring the values in the storage block
  • Encapsulating the values in an associated function

We'll begin by defining the Owner & Role data structures and implement a default constructor on the Owner.

struct Owner {
    maximum_owners: u64,
    role: Role,
}

impl Owner {
    // a constructor that can be evaluated to a constant `Owner` during compilation
    fn default() -> Self {
        Self {
            maximum_owners: 10,
            role: Role::FullAccess,
        }
    }
}

enum Role {
    FullAccess: (),
    PartialAcess: (),
    NoAccess: (),
}

Now that we have our data structures we'll keep track of how many current_owners we have and declare the owner in the two aformentioned styles.

storage {
    current_owners: u64 = 0,
    explicit_declaration: Owner = Owner {
        maximum_owners: 10,
        role: Role::FullAccess,
    },
    encapsulated_declaration: Owner = Owner::default(),
}

An explicit declaration is likely to be sufficient for most types. However, it may be preferable to encapsulate the initialization of complex types within a constructor in order to keep the code concise.

Note that the constructors used in storage blocks must evaluate to a constant during compilation.

Reading & Writing

When dealing with storage we have two options, we can either read from or write to storage. In both cases we must use a storage annotation to indicate the purity of the function.

When referencing a variable in storage we must explicitly indicate that the variable comes from storage and not a local scope.

This is done via the syntax storage.variable_name e.g. storage.counter.

storage {
    counter: u64 = 0,
}

Reading from Storage

When dealing with a built-in type we can retrieve the variable without the use of any special methods.

#[storage(read)]
fn read() {
    let counter = storage.counter.read();
}

Writing to Storage

When dealing with a built-in type we can update the variable without the use of any special methods.

#[storage(write)]
fn write() {
    storage.counter.write(storage.counter.read() + 1);
}

Read & Write

We can read and write to storage by using both keywords in the attribute.

#[storage(read, write)]
fn read_write() {
    let counter = storage.counter.read();
    storage.counter.write(counter + 1);
}

Libraries

The standard library provides additional utility for handling storage.

  • Mapping: Tracking key-value pairs
  • Vectors: Using a persistent (not heap-allocated) vector
  • Store & Get: Manually manipulating storage slots

StorageMap

A StorageMap, a.k.a. a hash table, is a structure which associates a value v with a key k. The key is used to find the position in the table (memory) where the value is stored.

The benefit of a hash table is that no matter where the value is in the table the computation required to find the location of that value is constant i.e. it has an order of 1 O(1).

Sway provides a flexible StorageMap because it uses generics for both k & v with the caveat that k and v have to be a single value. The value can be a struct, tuple, array etc. therefore if you'd like to have a complex k or v then the data needs to be wrapped into a single type.

Declaration

The StorageMap type is included in the prelude therefore we do not need to import it. We'll be using msg_sender() in the subsequent section so we'll import that here.

After the import we initialize our StorageMap as described in the initialization section.


storage {
    // k = Identity, v = u64
    balance: StorageMap<Identity, u64> = StorageMap::<Identity, u64> {},
    // k = (Identity, u64), v = bool
    user: StorageMap<(Identity, u64), bool> = StorageMap::<(Identity, u64), bool> {},
}

There are two storage variables: balance & user. balance takes a single value as the key while user wraps two values into a tuple and uses that as a key.

Reading from Storage

Retrieving data from a storage variable is done through the .get(key) method. That is to say that we state which storage variable we would like to read from and append .get() to the end while providing the key for the data that we want to retrieve. The method get returns an Option; if there is no value for key in the map, get will return None.

In this example we wrap the Identity of the caller with their provided id into a tuple and use that as the key.

#[storage(read)]
fn reading_from_storage(id: u64) {
    let user = storage.user.get((msg_sender().unwrap(), id)).read();
}

This contract method handles the returned Option by calling unwrap_or to set user to zero if the map user doesn't have an entry for the key.

Writing to Storage

Writing to storage is similar to reading. The difference is that we use a different method .insert(key, value).

In this example we retrieve the balance of the caller and then increment their balance by 1.

#[storage(read, write)]
fn writing_to_storage() {
    let balance = storage.balance.get(msg_sender().unwrap()).read();
    storage.balance.insert(msg_sender().unwrap(), balance + 1);
}

StorageVec

A StorageVec is a vector that permanently stores its data in storage. It replicates the functionality of a regular vector however its data is not stored contigiously because it utilizes hashing and generics to find a location to store the value T.

There is a number of methods in the standard library however we will take a look at pushing and retrieving data.

Declaration

To use a StorageVec we need to import it from the standard library and while we're at it we'll import the msg_sender() so that we can use it in the following section.

After the import we initialize our StorageVec as described in the initialization section.

use std::storage::storage_vec::*;

storage {
    // T = u64
    balance: StorageVec<u64> = StorageVec {},
    // T = (Identity, u64)
    user: StorageVec<(Identity, u64)> = StorageVec {},
}

There are two storage variables: balance & user. balance takes a single value while user wraps two values into a tuple.

Reading from Storage

Retrieving data from a storage variable is done through the .get(index) method. That is to say that we state which index by specifying it inside .get() and appending that to the end of the storage variable.

In this example we look at how we can retrieve a single value balance and how we can unpack multiple values from user.

#[storage(read)]
fn reading_from_storage(id: u64) {
    let balance = storage.balance.get(id).unwrap();

    let (user, value) = storage.user.get(id).unwrap().read();
}

Writing to Storage

Writing to storage is similar to reading. The difference is that we use a different method .push(value) and we use the read keyword because the implementation reads the length of the vector to determine where to store the value.

In this example we insert a tuple containing an the Identity of the caller and some id into the vector.

#[storage(read, write)]
fn writing_to_storage(id: u64) {
    storage.user.push((msg_sender().unwrap(), id));
}

Store & Get

Storage can be manipulated directly through the use of store() & get() functions. They utilize generics to store and retrieve values.

Declaration

To use store() & get() we must import them however we are not required to declare a storage block.

use std::storage::storage_api::{read, write};

Store

To store a generic value T we must provide a key of type b256.

In this example we store some number of type u64.

#[storage(write)]
fn store(key: b256, value: u64) {
    // write(key, SLOT, T) where T = generic type
    write(key, 0, value);
}

Get

To retrieve a generic value T at the position of key we must specify the type that we are retrieving.

In this example we retrieve some u64 at the position of key.

#[storage(read)]
fn get(key: b256) {
    // read::<T>(key, SLOT) where T = generic type
    let value = read::<u64>(key, 0);
}

The function get returns an Option; if the storage slots requested have not been set before, get will return None.

Assertions

An assertion is a condition which must evaluate to the Boolean value of true and its purpose is to prevent undesirable computation when the condition is evaluated to false.

For example, a function may only work if the condition argument < 5 is true. We can use an assertion to enforce this condition by:

  • Forcing a revert in the program when 5 <= argument
  • Handling the exception with additional code

Handling exceptions may be done through if expressions therefore the following sections will take a look at how we can make the virtual machine revert (safely crash).

  • assert: Checks if a condition is true otherwise reverts
  • require: Checks if a condition is true otherwise logs a value and reverts
  • revert: Reverts the virtual machine with the provided exit code
  • assert_eq: Checks if a and b are equal otherwise reverts
  • assert_ne: Checks if a and b are not equal otherwise reverts

assert

The assert function is automatically imported into every program from the prelude and it takes an expression which must evaluate to a Boolean. If the Boolean is true then nothing will happen and the code will continue to run otherwise the virtual machine will revert.

Example

Here we have a function which takes two u64 arguments and subtracts them. A u64 cannot be negative therefore the assertion enforces that b must be less than or equal to a.

If the condition is not met, then the virtual machine will revert.

fn subtract(a: u64, b: u64) -> u64 {
    assert(b <= a);
    a - b
}

require

The require function is automatically imported into every program from the prelude and it takes an expression which must evaluate to a Boolean. If the Boolean is true then nothing will happen and the rest of the code will continue to run otherwise a log will be emitted and the virtual machine will revert.

Example

Here we have a function which takes two u64 arguments and subtracts them. A u64 cannot be negative therefore the assertion enforces that b must be less than or equal to a.

If the condition is not met then the message b is too large will be logged and the virtual machine will revert.

The message is generic therefore it can be any type, in this example it's a string.

fn subtract(a: u64, b: u64) -> u64 {
    require(b <= a, "b is too large");
    a - b
}

revert

The revert function is automatically imported into every program from the prelude and it takes a u64 as an exit code.

The function will behave differently depending on the context in which it is used:

  • When used inside a predicate the function will panic and crash the program
  • Otherwise it will revert the virtual machine

Example

To manually force a revert we need to provide an exit code. To be able to distinguish one revert from another different exit codes can be used in different places.

    revert(42);

assert_eq

The assert_eq function is automatically imported into every program from the prelude. It takes two expressions which are compared and the result is a Boolean. If the value is false then the virtual machine will revert.

Example

Here is a function which asserts that a and b must be equal.

fn compare_eq(a: u64, b: u64) {
    assert_eq(a, b);
    // code
}

assert_ne

The assert_ne function is automatically imported into every program from the prelude. It takes two expressions which are compared and the result is a Boolean. If the value is false then the virtual machine will revert.

Example

Here is a function which asserts that a and b must not be equal.

fn compare_ne(a: u64, b: u64) {
    assert_ne(a, b);
    // code
}

Address Namespace

Sway utilizies namespaces to distinguish between address types.

Having multiple address types enforces type-safety and expands the range of values that an address can take because the same value can be used across multiple types.

The main types are:

For ease of use there is an enum wrapper Identity which contains both types.

Address

In the UTXO model each output has an address.

The Address type is a struct containing a value of a b256 type.

pub struct Address {
    bits: b256,
}

The value of an Address is a hash of either:

The Address type is completely separate from a ContractId and thus it should not be used when dealing with an address of a deployed contract.

Casting between an Address and b256 can be done in the following way:

    let variable1 = 0x000000000000000000000000000000000000000000000000000000000000002A;
    let my_address = Address::from(variable1);
    let variable2: b256 = my_address.into();
    // variable1 == variable2

ContractId

A contract's ID is a unique, deterministic identifier analogous to a contract's address in the EVM. Contracts cannot own UTXOs but they can own assets.

The ContractId type is a struct containing a value of a b256 type.

pub struct ContractId {
    bits: b256,
}

Casting between an ContractId and b256 can be done in the following way:

    let variable1 = 0x000000000000000000000000000000000000000000000000000000000000002A;
    let my_contract_id = ContractId::from(variable1);
    let variable2: b256 = my_contract_id.into();
    // variable1 == variable2

Identity

The Identity type is an enum that allows for the handling of both Address and ContractId types. This is useful in cases where either type is accepted, e.g. receiving funds from an identified sender, but not caring if the sender is an address or a contract.

An Identity is implemented as follows.

pub enum Identity {
    Address: Address,
    ContractId: ContractId,
}

Casting to an Identity must be done explicitly:

    let address = 0xddec0e7e6a9a4a4e3e57d08d080d71a299c628a46bc609aab4627695679421ca;
    let my_address_identity = Identity::Address(Address::from(address));
    let my_contract_identity = Identity::ContractId(ContractId::from(address));

Call Data

The term call-data refers to the metadata that is available to the recipient of a call.

In the following sections we'll cover the following call-data:

Message Sender

The standard prelude imports a function msg_sender() automatically, which retrieves the Identity of the caller.

The identity can be used for a variety of reasons however a common application is access control i.e. restricting functionality for non-privileged users (non-admins).

Example

We can implement access control by specifying that only the owner can call a function.

In the following snippet we accomplish this by comparing the caller msg_sender() to the OWNER. If a regular user calls the function then it will revert otherwise it will continue to run when called by the OWNER.

const OWNER = Identity::Address(Address::from(0x0000000000000000000000000000000000000000000000000000000000000000));

fn update() {
    require(msg_sender().unwrap() == OWNER, "Owner Only");
    // code
}

Asset Sent

The standard library provides a function msg_asset_id() which retrieves the ContractId of the asset being sent.

This can be used to determine which asset has been sent into the contract.

Example

To use msg_asset_id() we must import it from the standard library. We'll also import the base asset for comparison.

use std::call_frames::msg_asset_id;

We can check which asset has been sent and perform different computation based on the type.

fn deposit() {
    if msg_asset_id() == AssetId::base() {
        // code
    } else {
        // code
    }
}

Amount of Asset Sent

The standard library provides a function msg_amount() which retrieves the amount of asset sent without any concern for which asset is sent.

This can be used to set a price or manually track the amount sent by each user.

Example

To use msg_amount() we must import it from the standard library.

use std::context::msg_amount;

We can check how much of any asset has been sent and if an incorrect amount has been sent then we may revert.

fn purchase() {
    require(msg_amount() == 100_000_000, "Incorrect amount sent");
    // code
}

Logging

Logging is a way to record data as the program runs.

The standard library provides a logging module which contains a generic log function that is used to log a variable of any type.

Each call to log appends 1 of 2 types of a receipt to the list of receipts

  • Log
    • Generated for non-reference types: bool, u8, u16, u32, and u64
  • LogData
    • Generated for reference types and u256

The Rust & Typescript SDKs may be used to decode the data.

Example

To use the log function we must import it from the standard library and pass in any generic type T that we want to log.

fn log_data(number: u64) {
    // generic T = `number` of type `u64`
    log(number);
}

In the example above a u64 is used however we can pass in any generic type such as a struct, enum, string etc.

Call a Contract

A common blockchain operation is communication between smart contracts.

Example

To perform a call there are three steps that we must take:

  1. Provide an interface to call
  2. Create a type that allows us to make a call
  3. Call a function on our interface

Defining the Interface

Let's take the example of a Vault to demonstrate how a call can be performed.

library;

abi Vault {
    #[payable]
    fn deposit();
    fn withdraw(amount: u64, asset: ContractId);
}

Creating a Callable Type

To call a function on our Vault we must create a type that can perform calls. The syntax for creating a callable type is: abi(<interface-name>, <b256-address>).

Calling a Contract

The following snippet uses a script to call our Vault contract.

script;

use contract_interface::Vault;

fn main(amount: u64, asset_id: ContractId, vault_id: b256) -> bool {
    let caller = abi(Vault, vault_id);

    // Optional arguments are wrapped in `{}`
    caller.deposit {
        // `u64` that represents the gas being forwarded to the contract
        gas: 10000,
        // `u64` that represents how many coins are being forwarded
        coins: amount,
        // `b256` that represents the asset ID of the forwarded coins 
        asset_id: asset_id.into(),
    }();

    caller.withdraw(amount, asset_id);

    true
}

The deposit() function uses pre-defined optional arguments provided by the Sway language.

Re-entrancy

Re-entrancy occurs when a contract makes a call back into the contract that called it, e.g. Contract A calls Contract B but then Contract B makes a call back into Contract A.

To mitigate security concerns there are two approaches that are commonly used:

Re-entrancy Guard

Sway provides a stateless re-entrancy guard, which reverts at run-time when re-entrancy is detected.

To use the guard we must import it.

use reentrancy::reentrancy_guard;

Then call it in a contract function.

    fn deposit() {
        reentrancy_guard();

        // code
    }

Checks-Effects-Interactions Pattern

The pattern states that all state (storage) changes should be made before a call is made.

    fn withdraw() {
        // Step 1. Perform any state changes to update balance
        // Step 2. After all state changes make a call
    }

Asset Operations

A common application of a smart contract is the creation of an asset / token i.e. a cryptocurrency.

Managing a cryptocurrency is typically done via the following models:

  • Account based e.g. Ethereum
  • Unspent Transaction Output (UTXO) e.g. Bitcoin

Sway operates on the UTXO model therefore assets can be transferred out of the contract that created them. What this means is that keeping track of assets that have been transferred out of the contract may be more difficult because the information is not centralized in one place.

With that regard in mind, the account based approach can be partially replicated while utilizing certain asset operations that are build into the FuelVM.

The following sections will take a look at how an asset can be:

While also taking a look at:

Minting

Minting an asset means to create a new asset with an id of the contract that created it.

The standard library contains a module that can be used to mint an asset.

There are four functions that can be used to mint:

Mint

To use the function we must import it.

use std::asset::mint;

To mint some amount of an asset we specify the amount that we would like to mint and pass it into the mint() function.

    let amount = 10;
    mint(ZERO_B256, amount);

Mint to Address

We can mint and transfer the asset to an Address.

To use the function we must import it.

use std::asset::mint_to_address;

To mint some amount of an asset we specify the amount that we would like to mint and the Address to send it to.

    let amount = 10;
    let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
    let user = Address::from(address);

    mint_to_address(user, ZERO_B256, amount);

Mint to Contract

We can mint and transfer the asset to an Contract.

To use the function we must import it.

use std::asset::mint_to_contract;

To mint some amount of an asset we specify the amount that we would like to mint and the ContractId to send it to.

    let amount = 10;
    let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
    let pool = ContractId::from(address);

    mint_to_contract(pool, ZERO_B256, amount);

Mint to Address or Contract

We can mint and transfers to an Address or a Contract.

To use the function we must import it.

use std::asset::mint_to;

To mint some amount of an asset we specify the amount that we would like to mint and the Identity to send it to.

    let amount = 10;
    let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
    let user = Identity::Address(Address::from(address));
    let pool = Identity::ContractId(ContractId::from(address));

    mint_to(user, ZERO_B256, amount);
    mint_to(pool, ZERO_B256, amount);

Burning

Burning an asset means to destroy an asset that a contract has minted.

The standard library contains a module that can be used to burn an asset.

There is one function used to burn:

To use the function we must import it.

use std::asset::burn;

burn

To burn some amount of an asset we specify the amount that we would like to burn and pass it into the burn() function.

    let amount = 10;
    burn(ZERO_B256, amount);

Transfer

The standard library contains a module that can be used to transfer (send) an asset from one owner to another.

There are three functions that can be used to transfer an asset:

To Address

To use the function we must import it.

use std::asset::transfer;

To transfer some amount of an asset we specify the amount that we would like to transfer, the asset and the Address to send it to.

    let amount = 10;
    let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
    let asset = AssetId::base();
    let user = Address::from(address);

    transfer_to_address(user, asset, amount);

To Contract

To use the function we must import it.

use std::asset::force_transfer_to_contract;

To transfer some amount of an asset we specify the amount that we would like to transfer, the asset and the ContractId to send it to.

    let amount = 10;
    let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
    let asset = AssetId::base();
    let pool = ContractId::from(address);

    force_transfer_to_contract(pool, asset, amount);

To Address or Contract

To use the function we must import it.

use std::asset::transfer;

To transfer some amount of an asset we specify the amount that we would like to transfer, the asset and the Identity to send it to.

    let amount = 10;
    let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
    let asset = AssetId::base();
    let user = Identity::Address(Address::from(address));
    let pool = Identity::ContractId(ContractId::from(address));

    transfer(user, asset, amount);
    transfer(pool, asset, amount);

Contract Balance

Hashing

The hash module contains the following functions:

They take one generic argument T and return a b256 (hash of T).

To hash multiple values the values must be wrapped into one type such as a tuple, array, struct & enum.

sha256

To use the sha256 function we must import it.


To hash multiple values we wrap them into a tuple however other compound types may be used.

fn sha256_hashing(age: u64, name: str, status: bool) -> b256 {
    let mut hasher = Hasher::new();
    age.hash(hasher);
    hasher.write_str(name);
    status.hash(hasher);
    hasher.sha256()
}

keccak256

To use the keccak256 function we must import it.


To hash multiple values we wrap them into a tuple however other compound types may be used.

fn keccak256_hashing(age: u64, name: str, status: bool) -> b256 {
    let mut hasher = Hasher::new();
    age.hash(hasher);
    hasher.write_str(name);
    status.hash(hasher);
    hasher.keccak256()
}

Signature Recovery

Fuel Address

Ethereum Address

Counter

The following example implements a counter which is able to:

  • Increment the count by 1
  • Decrement the count by 1
  • Retrieve the value of the counter

ABI

To create a counter we must define an ABI which exposes methods that manipulate the count and retrieve its value. Since we are handling storage we must provide storage annotations on the functions.

abi Counter {
    #[storage(read, write)]
    fn increment();

    #[storage(read, write)]
    fn decrement();

    #[storage(read)]
    fn count() -> u64;
}

Implementation

We initialize a count in storage with the value of zero and implement methods to increment & decrement the count by one and return the value.

storage {
    counter: u64 = 0,
}

impl Counter for Contract {
    #[storage(read, write)]
    fn increment() {
        storage.counter.write(storage.counter.read() + 1);
    }

    #[storage(read, write)]
    fn decrement() {
        storage.counter.write(storage.counter.read() - 1);
    }

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

Fizzbuzz

The following example implements the fizzbuzz game.

The rules are:

  • A number divisible by 3 returns Fizz
  • A number divisible by 5 returns Buzz
  • A number which is divisible by 3 & 5 returns Fizzbuzz
  • Any other number entered is returned back to the user

State

Let's define an enum which contains the state of the game.

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

Implementation

We can write a function which takes an input and checks its divisibility. Depending on the result a different State will be returned.

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

Contract Ownership

The following example implements access control to restrict functionality to a privileged user.

ABI

The interface contains a function to set the owner and a function that only the owner can use.

abi Ownership {
    #[storage(read, write)]
    fn set_owner(owner: Option<Identity>);

    #[storage(read)]
    fn action();
}

Identity

We must keep track of the owner in storage and compare them against the caller via msg_sender().

Initially there is no owner so we'll set them to None.

storage {
    owner: Option<Identity> = None,
}

Implementation

To set the owner one of two conditions must be met:

  • There is no owner
  • The current owner is calling the function

To call our action() function the caller must be the owner of the contract.

impl Ownership for Contract {
    #[storage(read, write)]
    fn set_owner(owner: Option<Identity>) {
        assert(storage.owner.read().is_none() || storage.owner.read().unwrap() == msg_sender().unwrap());
        storage.owner.write(owner);
    }

    #[storage(read)]
    fn action() {
        assert(storage.owner.read().unwrap() == msg_sender().unwrap());
        // code
    }
}

Wallet

The following example implements a wallet that utilizes the base asset.

ABI

The interface contains a function which tracks the amount of the base asset received and a function to transfer the funds.

abi Wallet {
    #[storage(read, write)]
    fn receive();

    #[storage(read, write)]
    fn send(amount: u64, recipient: Identity);
}

Implementation

When receiving funds we assert that the wallet accepts the base asset and we track the amount sent. When transferring funds out of the wallet we assert that only the owner can perform the transfer.

use std::{
    call_frames::msg_asset_id,
    context::msg_amount,
    asset::transfer,
};

storage {
    balance: u64 = 0,
}

const OWNER = Address::from(0x8900c5bec4ca97d4febf9ceb4754a60d782abbf3cd815836c1872116f203f861);

impl Wallet for Contract {
    #[storage(read, write)]
    fn receive() {
        assert(msg_asset_id() == AssetId::base());
        storage.balance.write(storage.balance.read() + msg_amount());
    }

    #[storage(read, write)]
    fn send(amount: u64, recipient: Identity) {
        assert(msg_sender().unwrap() == Identity::Address(OWNER));
        storage.balance.write(storage.balance.read() - amount);
        transfer(recipient, AssetId::base(), amount);
    }
}

Liquidity Pool

Standard Library Prelude

The prelude is a list of commonly used features from the standard library which is automatically imported into every Sway program.

The prelude contains the following:

  • Address: A struct containing a b256 value which represents the wallet address
  • ContractId A struct containing a b256 value which represents the ID of a contract
  • Identity: An enum containing Address & ContractID structs
  • Vec: A growable, heap-allocated vector
  • StorageMap: A key-value mapping in contract storage
  • Option: An enum containing either some generic value <T> or an absence of that value, we also expose the variants directly:
    • Some
    • None
  • Result: An enum used to represent either a success or failure of an operation, we also expose the variants directly:
    • Ok
    • Err
  • assert: A module containing
    • assert: A function that reverts the VM if the condition provided to it is false
    • assert_eq: A function that reverts the VM and logs its two inputs v1 and v2 if the condition v1 == v2 is false
    • assert_ne: A function that reverts the VM and logs its two inputs v1 and v2 if the condition v1 != v2 is false
  • revert: A module containing
    • require: A function that reverts and logs a given value if the condition is false
    • revert: A function that reverts
  • log: A function that logs arbitrary stack types
  • msg_sender: A function that gets the Identity from which a call was made

Language Comparison

Rust

Solidity

Software Development Kits

Rust SDK

TypeScript SDK

Advanced Concepts

Struct Memory Layout

Structs have zero memory overhead, meaning that each field is laid out sequentially in memory. No metadata regarding the struct's name or other properties is preserved at runtime.

In other words, structs are compile-time constructs similar to Rust, but different in other languages with runtimes like Java.

Enum Memory Layout

Enums 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. To calculate the size of an enum in memory, add 8 bytes to the size of the largest variant.

Examples

The following examples consist of enums with two variants.

The largest variant for Example One is the u64 and b256 for Example Two.

Example One

The size of enum T is 16 bytes, 8 bytes for the tag and 8 bytes for the u64.

pub enum T {
    a: u64,
    b: (),
}

Instantiating the u64 type will take up 16 bytes.

    let a = T::a(42);

Instantiating the unit type will take up 16 bytes.

    let b = T::b;

Example Two

The size of enum K is 40 bytes, 8 bytes for the tag and 32 bytes for the b256.

pub enum K {
    a: b256,
    b: u64,
}

Instantiating the b256 type will take up 40 bytes.

    let a = K::a(0x0000000000000000000000000000000000000000000000000000000000000000);

Instantiating the u64 type will take up 40 bytes.

    let b = K::b(42);

Compiler Intrinsics

Known Issues and Workarounds

General

TODO: need help filling this in, might remove this page and move content into individual sections

  • Issue: #870
    • All impl blocks need to be defined before any of the functions they define can be called. This includes sibling functions in the same impl declaration, i.e., functions in an impl can't call each other yet.

Missing Features

Importing

In external libraries we have looked at how a library can be imported into a project so that code can be reused.

When it comes to importing only external libraries can be imported through the Forc.toml file; any other type of program will result in an error.

This means that the following projects cannot be imported:

While contracts cannot be imported, a workaround is to move the contract's abi declaration into an external library and import that library anywhere the ABI is needed.

TODO: move the next comment into a page where it makes sense to keep it

Furthermore, using contract dependencies it is possible to import the contract ID automatically as a public constant.

Strings

Sway strings are declared using double-quotes ". Single quotes ' cannot be used. Attempting to define a string with single-quotes will result in an error.

    // Will error if uncommented
    // let fuel = 'fuel';

Strings are UTF-8 encoded therefore they cannot be indexed.

    let fuel = "fuel";
    // Will error if uncommented
    // let f = fuel[0];

Predicates

A predicate does not have any side effects because it is pure and thus it cannot create receipts.

Since there are no receipts they cannot use logging nor create a stack backtrace for debugging. This means that there is no way to debug them aside from using a single-stepping debugger.

As a workaround, the predicate can be written, tested, and debugged first as a script, and then changed back into a predicate.

Pattern Matching

Nested Match Expressions

In nested match expressions we nest a match expression by embedding it inside the {} brackets on the right side of the arrow =>.

Match expressions cannot be used as a pattern, the left side of the arrow =>.

Constants

When matching on constants we specify that a constant must be used in order to match on a variable. Dynamic values, such as an argument to a function, cannot be matched upon because it will be treated as a catch_all case and thus any subsequent patterns will not be checked.

Storage

Manifest Reference