Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/master/reference/.nojekyll b/master/reference/.nojekyll new file mode 100644 index 00000000000..f17311098f5 --- /dev/null +++ b/master/reference/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/master/reference/404.html b/master/reference/404.html new file mode 100644 index 00000000000..98c9008474e --- /dev/null +++ b/master/reference/404.html @@ -0,0 +1,188 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +The following example implements access control to restrict functionality to a privileged user.
+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();
+}
+
+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,
+}
+
+
+To set the owner one of two conditions must be met:
+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
+ }
+}
+
+
+ The following example implements a counter which is able to:
+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;
+}
+
+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()
+ }
+}
+
+
+ The following example implements the fizzbuzz game.
+The rules are:
+3
returns Fizz
5
returns Buzz
3
& 5
returns Fizzbuzz
Let's define an enum
which contains the state of the game.
enum State {
+ Fizz: (),
+ Buzz: (),
+ FizzBuzz: (),
+ Other: u64,
+}
+
+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)
+ }
+}
+
+
+ The following example implements a wallet that utilizes the base asset.
+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);
+}
+
+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);
+ }
+}
+
+
+ Cargo can be used to install the Sway toolchain with various plugins
.
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)
+
+The Sway toolchain
can be installed/updated with:
cargo install forc
+
+
+ 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
.
Fuel Core
can be installed/updated with:
cargo install fuel-core
+
+There may be additional system dependencies
required for installation.
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
+
+
+ The Sway toolchain
is required to compile Sway
programs.
There are three ways to install the Sway toolchain
:
Fuelup
Cargo
From Source
The supported operating systems include Linux and macOS; however, Windows is unsupported
.
Fuelup
is the recommended tool for installation and management of the toolchain.
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.
The latest features may be accessed when installing from source
; however, the features may not be ready for release and lead to unstable behavior.
The Sway toolchain
can be built directly from the Sway repository
.
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
.
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
+
+
+ The #[allow(dead_code)]
annotation disables warnings which are emitted by the compiler for code that is unused.
#[allow(dead_code)]
+fn unused_function() {}
+
+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 {};
+}
+
+
+ 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 {};
+}
+
+
+ An attribute is a metadatum which provides some additional functionality.
+ +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.
To suggest code generation use the never
keyword.
#[inline(never)]
+fn foo() {}
+
+To suggest code copy use the always
keyword.
#[inline(always)]
+fn bar() {}
+
+
+ A storage attribute indicates the purity of a function i.e. whether it:
+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.
+When we read from storage we use the read
keyword.
#[storage(read)]
+
+When we write to storage we use the write
keyword.
#[storage(write)]
+
+When we read from and write to storage we use the read
& write
keywords.
#[storage(read, write)]
+
+
+ Sway provides the #[test]
attribute which enables unit tests to be written in Sway.
The #[test]
attribute indicates that a test has passed if it did not revert.
#[test]
+fn equal() {
+ assert_eq(1 + 1, 2);
+}
+
+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);
+}
+
+
+ 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.
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
.
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;
+
+
+ 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:
true
to false
false
to true
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
+}
+
+
+ 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;
+
+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,
+ });
+}
+
+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);
+}
+
+
+ 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.
+Sway has the following primitive types:
+u8
(8-bit unsigned integer)u16
(16-bit unsigned integer)u32
(32-bit unsigned integer)u64
(64-bit unsigned integer)u256
(256-bit unsigned integer)hexadecimal
, binary
& base-10
syntaxbool
(true or false)str
(string slice)str[n]
(fixed-length string of size n)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 are types that group multiple values into one type.
+Sway has the following compound types:
+ + +Broadly speaking there are two types of 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:
+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.
+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:
+For those inclined, the range for an n-bit unsigned integer is 0 to 2n-1.
+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;
+
+
+ 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:
+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.
+ +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.
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.
+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.
+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 ..
.
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.
+To declare a tuple we wrap the values in ()
.
// Define a tuple containing 2 u64 types
+ let mut balances = (42, 1337);
+
+Values can be retrieved individually from the tuple by specifying the index.
+ // first = 42, second = 1337
+ let first = balances.0;
+ let second = balances.1;
+
+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);
+
+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;
+
+
+ There are two kinds of comments in Sway.
+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 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 }
+}
+
+
+ Sway supports if, else, and else if expressions which provide control over which instructions should be executed depending on the conditions.
+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 encompasses all the other conditions which we do not care about or can be treated in the same way.
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.
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: (),
+}
+
+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;
+ }
+
+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
.
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.
+ +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
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
.
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:
+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.
+counter
is less than 10
then continue to iteratecondition
variable is true
then continue to iterateAs 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
.
Sway also allows nested while
loops.
while true {
+ // computation here
+ while true {
+ // more computation here
+ }
+ }
+
+
+ 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,
+ };
+}
+
+
+ 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,
+ };
+}
+
+
+ Match expressions are meant to cover advanced patterns so the following sections demonstrate some examples:
+ + +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,
+ }
+ }
+}
+
+
+ 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,
+ }
+ },
+ }
+}
+
+
+ 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 ..
.
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:
+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,
+ };
+
+
+ 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.
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.
+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.
+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 }
+ }
+}
+
+To call an associated function on a type we use the following syntax.
+ let foo = Foo::new(42);
+
+
+ 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.
+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
+}
+
+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`
+
+
+ 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:
+self
as the first parameterself
parameterA function declaration consists of a few components
+fn
keyword()
Here is a template that applies to the aforementioned functions.
+fn my_function(my_parameter: u64 /* ... */ ) -> u64 {
+ // function code
+ 42
+}
+
+
+ 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.
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;
+ }
+}
+
+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
+
+
+ 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
.
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);
+}
+
+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)
+ }
+}
+
+
+ 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:
+ + +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);
+}
+
+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
+ }
+}
+
+
+ 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:
+true
in order for the transaction to be considered validA 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:
+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:
+This means that a project cannot contain more than one contract, one script, one predicate and it cannot mix them together.
+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.
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
+
+my_other_library
has a function quix()
which can be imported into my_library
because it uses the pub
keyword.
library;
+
+pub fn quix() {}
+
+To be able to use quix()
inside my_library
there are two steps to take.
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" }
+
+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
+
+
+ A library is used to contain code that performs common operations in order to prevent code duplication.
+Libraries are defined using the library
keyword at the beginning of a file.
library;
+
+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:
+pub
keyword at the start of some codeForc.toml
file as a dependency and then import the pub
declarationlibrary;
+
+// 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
:
Libraries cannot be directly deployed to a blockchain, but they can be deployed as part of a contract.
+ +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:
mod
keyword followed by the library nameuse
keyword to selectively import various items from the librarylibrary;
+
+mod my_library;
+
+use my_library::bar;
+
+// `bar` from `my_library` is now available throughout the file
+
+
+ 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.
+In Fuel, coins can be sent to an address uniquely representing a particular predicate's bytecode (the bytecode root, calculated here).
+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
.
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.
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
+}
+
+
+ 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:
+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
+}
+
+
+ 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;
+}
+
+
+ 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.
+ +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: (),
+}
+
+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;
+
+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:
+Error
, StateError
& UserError
Functions that return values typically follow one of two styles:
+get_
to the start of the nameget_
In Sway the encouraged usage is to omit the get_
prefix.
fn maximum_deposit() -> u64 {
+ 100
+}
+
+That is to say the following is discouraged.
+fn get_maximum_deposit() -> u64 {
+ 100
+}
+
+
+ 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
+
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.
+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()`
+}
+
+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()`
+}
+
+
+ A naming convention is a set of rules used to standardize how code is written.
+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: (),
+}
+
+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
+}
+
+Constants are SCREAMING_SNAKE_CASE
which means that each word in capitalized and separated by an underscore.
const MAXIMUM_DEPOSIT = 10;
+
+
+ The following examples present pattern matching using the match
keyword for the catch-all case.
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,
+ };
+}
+
+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,
+ };
+}
+
+
+ In returning from functions we outline two styles of returning:
+return
keywordreturn
keywordIn general the preferred style is to follow the implicit return however both are perfectly acceptable.
+ +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 };
+}
+
+
+ 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
+}
+
+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
+}
+
+We may discard the context and the value by assigning it to _
.
fn nameless_variable() -> u64 {
+ let (_, deposit_amount) = call();
+
+ deposit_amount
+}
+
+
+ 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.
+Trait functions consist of just a function signature. This indicates that the implementation must define the function.
+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.
+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 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.
+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
+}
+
+
+
+ Constants are similar to immutable let variables; however, there are a few differences:
+impl
scope.mut
keyword cannot be used with constants.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
ConstantsConstants 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
+}
+
+
+ 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:
+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.
+ +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.
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.
We can declare a variable that can have its value changed through the use of the mut
keyword.
let mut foo = 5;
+ foo = 6;
+
+When assigning to a mutable variable, the right-hand side of the assignment is evaluated before the left-hand side. In the below example, the mutable variable i
will first be increased and the resulting value of 1
will be stored to array[1]
, thus resulting in array
being changed to [0, 1, 0]
.
let mut array = [0, 0, 0];
+ let mut i = 0;
+
+ array[i] = {
+ i += 1;
+ i
+ };
+
+
+ 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.
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";
+
+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.
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.
+The following examples consist of enums with two variants.
+The largest variant for Example One
is the u64
and b256
for Example Two
.
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;
+
+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);
+
+
+ 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.
+ +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 addressContractId
A struct containing a b256
value which represents the ID of a contractIdentity
: An enum containing Address
& ContractID
structsVec
: A growable, heap-allocated vectorStorageMap
: A key-value mapping in contract storageOption
: 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 falseassert_eq
: A function that reverts the VM and logs its two inputs v1 and v2 if the condition v1 == v2 is falseassert_ne
: A function that reverts the VM and logs its two inputs v1 and v2 if the condition v1 != v2 is falserevert
: A module containing
+require
: A function that reverts and logs a given value if the condition is false
revert
: A function that revertslog
: A function that logs arbitrary stack typesmsg_sender
: A function that gets the Identity from which a call was made++TODO: need help filling this in, might remove this page and move content into individual sections
+
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.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.
+ +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 =>
.
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.
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
.
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];
+
+
+ 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.
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
+}
+
+
+ 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.
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
+}
+
+
+ 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.
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
+}
+
+
+ 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:
5 <= argument
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 revertsrequire
: Checks if a condition
is true
otherwise logs a value
and revertsrevert
: Reverts the virtual machine with the provided exit codeassert_eq
: Checks if a
and b
are equal otherwise revertsassert_ne
: Checks if a
and b
are not equal otherwise revertsThe 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.
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
+}
+
+
+ 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:
+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);
+
+
+ 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:
+ +burn()
To use the function we must import it.
+use std::asset::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(SubId::zero(), amount);
+
+
+ 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:
+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:
+Minted
(created)Burned
(destroyed)Transferred
(sent)While also taking a look at:
+ + +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, SubId::zero(), amount);
+ mint_to(pool, SubId::zero(), amount);
+
+
+ We can mint
and transfer
the asset to an Address
.
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 Address
to send it to.
let amount = 10;
+ let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
+ let user = Address::from(address);
+
+ mint_to(Identity::Address(user), SubId::zero(), amount);
+
+
+ We can mint
and transfer
the asset to an 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 ContractId
to send it to.
let amount = 10;
+ let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
+ let pool = ContractId::from(address);
+
+ mint_to(Identity::ContractId(pool), SubId::zero(), amount);
+
+
+ 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 two functions that can be used to mint:
+ + +Specific implementation details on transferring assets to addresses can be found here.
+Specific implementation details on transferring assets to contracts can be found here.
+ +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(SubId::zero(), amount);
+
+
+ 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);
+
+
+ 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(Identity::Address(user), asset, amount);
+
+
+ To use the transfer 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 ContractId
to send it to.
let amount = 10;
+ let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
+ let asset = AssetId::base();
+ let pool = ContractId::from(address);
+
+ transfer(Identity::ContractId(pool), asset, amount);
+
+
+ The standard library contains a module
that can be used to transfer (send) an asset from one owner to another.
There is one function that can be used to transfer an asset to any entity:
+transfer()
Specific implementation details on transferring assets to addresses can be found here.
+Specific implementation details on transferring assets to contracts can be found here.
+ +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
: who is making the callAsset Sent
: which asset has been sent into the contractAmount of Asset Sent
: how much of an asset has been sentThe 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.
+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
+}
+
+
+ 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.
+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
+ }
+}
+
+
+ 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).
+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
+}
+
+
+ A common blockchain operation is communication between smart contracts.
+To perform a call there are three steps that we must take:
+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);
+}
+
+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>)
.
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.
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()
+}
+
+
+ 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()
+}
+
+
+ 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
+bool
, u8
, u16
, u32
, and u64
LogData
+u256
The Rust & Typescript SDKs may be used to decode the data.
+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.
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
+
+
+ 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
+
+
+ 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));
+
+
+ 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:
+Address
: Used to identify the UTXOContractId
: Used to identify a contractFor ease of use there is an enum wrapper Identity
which contains both types.
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:
+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
+ }
+
+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
+ }
+
+
+ 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:
storage
blockstorage
namespacesStorage 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,
+ // ...
+}
+
+In the following example we will take a look at two ways of storing a struct.
+storage
blockWe'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: (),
+ PartialAccess: (),
+ 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 aforementioned 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.
The standard library provides additional utility for handling storage.
+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.
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.
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 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);
+}
+
+
+ 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 contiguously 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.
+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.
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 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));
+}
+
+
+ Storage can be manipulated directly through the use of store()
& get()
functions. They utilize generics to store and retrieve values.
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};
+
+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);
+}
+
+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
.
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,
+}
+
+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();
+}
+
+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);
+}
+
+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);
+}
+
+
+ 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.
+ + +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.
+ +The Sway toolchain
is required to compile Sway
programs.
There are three ways to install the Sway toolchain
:
Fuelup
Cargo
From Source
The supported operating systems include Linux and macOS; however, Windows is unsupported
.
Fuelup
is the recommended tool for installation and management of the toolchain.
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.
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
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 can be used to install the Sway toolchain with various plugins
.
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)
+
+The Sway toolchain
can be installed/updated with:
cargo install forc
+
+The Sway toolchain
can be built directly from the Sway repository
.
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
.
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
+
+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
.
Fuel Core
can be installed/updated with:
cargo install fuel-core
+
+There may be additional system dependencies
required for installation.
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:
+true
in order for the transaction to be considered validA 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:
+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:
+This means that a project cannot contain more than one contract, one script, one predicate and it cannot mix them together.
+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.
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:
+ + +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);
+}
+
+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
+ }
+}
+
+A library is used to contain code that performs common operations in order to prevent code duplication.
+Libraries are defined using the library
keyword at the beginning of a file.
library;
+
+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:
+pub
keyword at the start of some codeForc.toml
file as a dependency and then import the pub
declarationlibrary;
+
+// 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
:
Libraries cannot be directly deployed to a blockchain, but they can be deployed as part of a contract.
+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:
mod
keyword followed by the library nameuse
keyword to selectively import various items from the librarylibrary;
+
+mod my_library;
+
+use my_library::bar;
+
+// `bar` from `my_library` is now available throughout the file
+
+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
+
+my_other_library
has a function quix()
which can be imported into my_library
because it uses the pub
keyword.
library;
+
+pub fn quix() {}
+
+To be able to use quix()
inside my_library
there are two steps to take.
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" }
+
+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
+
+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:
+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
+}
+
+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.
+In Fuel, coins can be sent to an address uniquely representing a particular predicate's bytecode (the bytecode root, calculated here).
+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
.
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.
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
+}
+
+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.
+Sway has the following primitive types:
+u8
(8-bit unsigned integer)u16
(16-bit unsigned integer)u32
(32-bit unsigned integer)u64
(64-bit unsigned integer)u256
(256-bit unsigned integer)hexadecimal
, binary
& base-10
syntaxbool
(true or false)str
(string slice)str[n]
(fixed-length string of size n)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 are types that group multiple values into one type.
+Sway has the following compound types:
+ +Broadly speaking there are two types of 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:
+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.
+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:
+For those inclined, the range for an n-bit unsigned integer is 0 to 2n-1.
+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;
+
+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:
true
to false
false
to true
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
+}
+
+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:
+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.
+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;
+
+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.
+To declare a tuple we wrap the values in ()
.
// Define a tuple containing 2 u64 types
+ let mut balances = (42, 1337);
+
+Values can be retrieved individually from the tuple by specifying the index.
+ // first = 42, second = 1337
+ let first = balances.0;
+ let second = balances.1;
+
+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);
+
+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;
+
+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
.
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.
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.
+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.
+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 ..
.
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;
+
+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,
+ });
+}
+
+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);
+}
+
+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:
+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.
+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.
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.
We can declare a variable that can have its value changed through the use of the mut
keyword.
let mut foo = 5;
+ foo = 6;
+
+When assigning to a mutable variable, the right-hand side of the assignment is evaluated before the left-hand side. In the below example, the mutable variable i
will first be increased and the resulting value of 1
will be stored to array[1]
, thus resulting in array
being changed to [0, 1, 0]
.
let mut array = [0, 0, 0];
+ let mut i = 0;
+
+ array[i] = {
+ i += 1;
+ i
+ };
+
+Constants are similar to immutable let variables; however, there are a few differences:
+impl
scope.mut
keyword cannot be used with constants.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
ConstantsConstants 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
+}
+
+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.
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";
+
+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.
There are two kinds of comments in Sway.
+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 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, 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:
+self
as the first parameterself
parameterA function declaration consists of a few components
+fn
keyword()
Here is a template that applies to the aforementioned functions.
+fn my_function(my_parameter: u64 /* ... */ ) -> u64 {
+ // function code
+ 42
+}
+
+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.
+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
+}
+
+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 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.
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;
+ }
+}
+
+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 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.
+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.
+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 }
+ }
+}
+
+To call an associated function on a type we use the following syntax.
+ let foo = Foo::new(42);
+
+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
.
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);
+}
+
+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)
+ }
+}
+
+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.
+Sway supports if, else, and else if expressions which provide control over which instructions should be executed depending on the conditions.
+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 encompasses all the other conditions which we do not care about or can be treated in the same way.
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.
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: (),
+}
+
+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;
+ }
+
+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
.
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:
+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.
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,
+ };
+
+Match expressions are meant to cover advanced patterns so the following sections demonstrate some examples:
+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,
+ };
+}
+
+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 ..
.
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,
+ };
+}
+
+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,
+ }
+ },
+ }
+}
+
+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,
+ }
+ }
+}
+
+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:
+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.
+counter
is less than 10
then continue to iteratecondition
variable is true
then continue to iterateAs 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
.
Sway also allows nested while
loops.
while true {
+ // computation here
+ while true {
+ // more computation here
+ }
+ }
+
+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
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
.
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.
An attribute is a metadatum which provides some additional functionality.
+A storage attribute indicates the purity of a function i.e. whether it:
+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.
+When we read from storage we use the read
keyword.
#[storage(read)]
+
+When we write to storage we use the write
keyword.
#[storage(write)]
+
+When we read from and write to storage we use the read
& write
keywords.
#[storage(read, write)]
+
+The payable annotation is used to allow a contract function to accept an asset forwarded via a call.
+To allow a contract to accept assets we use the payable
keyword.
#[payable]
+ fn deposit();
+
+Sway provides the #[test]
attribute which enables unit tests to be written in Sway.
The #[test]
attribute indicates that a test has passed if it did not revert.
#[test]
+fn equal() {
+ assert_eq(1 + 1, 2);
+}
+
+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);
+}
+
+The #[allow(dead_code)]
annotation disables warnings which are emitted by the compiler for code that is unused.
#[allow(dead_code)]
+fn unused_function() {}
+
+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 {};
+}
+
+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.
To suggest code generation use the never
keyword.
#[inline(never)]
+fn foo() {}
+
+To suggest code copy use the always
keyword.
#[inline(always)]
+fn bar() {}
+
+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 {};
+}
+
+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.
+Trait functions consist of just a function signature. This indicates that the implementation must define the function.
+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.
+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 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.
+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
+}
+
+
+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
+
A naming convention is a set of rules used to standardize how code is written.
+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: (),
+}
+
+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
+}
+
+Constants are SCREAMING_SNAKE_CASE
which means that each word in capitalized and separated by an underscore.
const MAXIMUM_DEPOSIT = 10;
+
+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;
+}
+
+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 };
+}
+
+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: (),
+}
+
+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;
+
+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:
+Error
, StateError
& UserError
In returning from functions we outline two styles of returning:
+return
keywordreturn
keywordIn general the preferred style is to follow the implicit return however both are perfectly acceptable.
+The following examples present pattern matching using the match
keyword for the catch-all case.
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,
+ };
+}
+
+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,
+ };
+}
+
+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.
+Functions that return values typically follow one of two styles:
+get_
to the start of the nameget_
In Sway the encouraged usage is to omit the get_
prefix.
fn maximum_deposit() -> u64 {
+ 100
+}
+
+That is to say the following is discouraged.
+fn get_maximum_deposit() -> u64 {
+ 100
+}
+
+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
+}
+
+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
+}
+
+We may discard the context and the value by assigning it to _
.
fn nameless_variable() -> u64 {
+ let (_, deposit_amount) = call();
+
+ deposit_amount
+}
+
+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.
+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()`
+}
+
+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()`
+}
+
+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:
storage
blockstorage
namespacesStorage 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,
+ // ...
+}
+
+In the following example we will take a look at two ways of storing a struct.
+storage
blockWe'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: (),
+ PartialAccess: (),
+ 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 aforementioned 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.
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,
+}
+
+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();
+}
+
+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);
+}
+
+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);
+}
+
+The standard library provides additional utility for handling storage.
+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.
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.
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 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);
+}
+
+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 contiguously 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.
+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.
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 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));
+}
+
+Storage can be manipulated directly through the use of store()
& get()
functions. They utilize generics to store and retrieve values.
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};
+
+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);
+}
+
+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
.
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:
5 <= argument
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 revertsrequire
: Checks if a condition
is true
otherwise logs a value
and revertsrevert
: Reverts the virtual machine with the provided exit codeassert_eq
: Checks if a
and b
are equal otherwise revertsassert_ne
: Checks if a
and b
are not equal otherwise revertsThe 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.
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
+}
+
+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.
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
+}
+
+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:
+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);
+
+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.
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
+}
+
+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.
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
+}
+
+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:
+Address
: Used to identify the UTXOContractId
: Used to identify a contractFor ease of use there is an enum wrapper Identity
which contains both types.
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
+
+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
+
+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));
+
+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
: who is making the callAsset Sent
: which asset has been sent into the contractAmount of Asset Sent
: how much of an asset has been sentThe 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).
+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
+}
+
+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.
+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
+ }
+}
+
+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.
+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 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
+bool
, u8
, u16
, u32
, and u64
LogData
+u256
The Rust & Typescript SDKs may be used to decode the data.
+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.
A common blockchain operation is communication between smart contracts.
+To perform a call there are three steps that we must take:
+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);
+}
+
+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>)
.
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 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:
+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
+ }
+
+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
+ }
+
+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:
+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:
+Minted
(created)Burned
(destroyed)Transferred
(sent)While also taking a look at:
+ +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 two functions that can be used to mint:
+ + +Specific implementation details on transferring assets to addresses can be found here.
+Specific implementation details on transferring assets to contracts can be found here.
+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(SubId::zero(), amount);
+
+We can mint
and transfer
the asset to an Address
.
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 Address
to send it to.
let amount = 10;
+ let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
+ let user = Address::from(address);
+
+ mint_to(Identity::Address(user), SubId::zero(), amount);
+
+We can mint
and transfer
the asset to an 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 ContractId
to send it to.
let amount = 10;
+ let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
+ let pool = ContractId::from(address);
+
+ mint_to(Identity::ContractId(pool), SubId::zero(), amount);
+
+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, SubId::zero(), amount);
+ mint_to(pool, SubId::zero(), amount);
+
+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:
+ +burn()
To use the function we must import it.
+use std::asset::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(SubId::zero(), amount);
+
+The standard library contains a module
that can be used to transfer (send) an asset from one owner to another.
There is one function that can be used to transfer an asset to any entity:
+transfer()
Specific implementation details on transferring assets to addresses can be found here.
+Specific implementation details on transferring assets to contracts can be found here.
+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(Identity::Address(user), asset, amount);
+
+To use the transfer 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 ContractId
to send it to.
let amount = 10;
+ let address = 0x0000000000000000000000000000000000000000000000000000000000000001;
+ let asset = AssetId::base();
+ let pool = ContractId::from(address);
+
+ transfer(Identity::ContractId(pool), asset, amount);
+
+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);
+
+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
.
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()
+}
+
+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()
+}
+
+The following example implements a counter which is able to:
+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;
+}
+
+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()
+ }
+}
+
+The following example implements the fizzbuzz game.
+The rules are:
+3
returns Fizz
5
returns Buzz
3
& 5
returns Fizzbuzz
Let's define an enum
which contains the state of the game.
enum State {
+ Fizz: (),
+ Buzz: (),
+ FizzBuzz: (),
+ Other: u64,
+}
+
+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)
+ }
+}
+
+The following example implements access control to restrict functionality to a privileged user.
+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();
+}
+
+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,
+}
+
+
+To set the owner one of two conditions must be met:
+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
+ }
+}
+
+The following example implements a wallet that utilizes the base asset.
+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);
+}
+
+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);
+ }
+}
+
+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 addressContractId
A struct containing a b256
value which represents the ID of a contractIdentity
: An enum containing Address
& ContractID
structsVec
: A growable, heap-allocated vectorStorageMap
: A key-value mapping in contract storageOption
: 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 falseassert_eq
: A function that reverts the VM and logs its two inputs v1 and v2 if the condition v1 == v2 is falseassert_ne
: A function that reverts the VM and logs its two inputs v1 and v2 if the condition v1 != v2 is falserevert
: A module containing
+require
: A function that reverts and logs a given value if the condition is false
revert
: A function that revertslog
: A function that logs arbitrary stack typesmsg_sender
: A function that gets the Identity from which a call was madeStructs 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.
+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.
+The following examples consist of enums with two variants.
+The largest variant for Example One
is the u64
and b256
for Example Two
.
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;
+
+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);
+
+++TODO: need help filling this in, might remove this page and move content into individual sections
+
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.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.
+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];
+
+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
.
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 =>
.
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.