Welcome

Thank you for taking the time to consider contributing to the project!

The aim of this guide is to introduce general concepts that can be taken across many projects in your professional career while directing them specifically at this project.

In this contributing guide, we will cover various topics starting from navigating GitHub issues to submitting a pull request for review.

We advise taking the time to carefully familiarize yourself with the content in each section. Every section has a purpose and it's likely that we will comment on any, if not all, sections in a pull request.

GitHub Issues

GitHub provides a ticketing system called issues which allows users to create and track the progress of tasks that need to be done.

When looking to contribute to a library one of the first places to look is their issues section while following the guidance that the authors of the project have provided in their contributing document.

The issue should describe the work that needs to be done and the contributing document should outline the expectations that must be met in order for the work to eventually make its way into the repository.

Searching for Issues

There are a few points to consider when looking for a task to contribute to. The following sections provide a quickstart guide for navigating the issues in the Sway Libs repository.

Filtering by label

Issues can be grouped into categories through the use of labels and depending on the category a user may choose to contribute to one task or another.

  • Is it a bug fix, improvement, documentation etc.
  • Is it for the compiler, user interface, tooling etc.
  • Is the priority critical and must be resolved immediately or is it a low priority "nice to have"?

Checking for available issues

Once the issues are filtered a task can be selected if another user is not currently assigned to that task otherwise multiple people may be working on the same issue when only one solution can be chosen.

Issue summary

Each issue should have a description which provides context into the problem and what may be done to resolve it.

Filtering by label

The default issues tab shows all of the issues that are currently open. GitHub already provides various search queries that can be made using the search bar however an easier way is to use the labels that the authors have provided to quickly filter for the relevant issues such as bugs, improvements, documentation, etc.

Under the Label tab you can select any number of labels and any issue that matches those labels will be shown while the other issues will be hidden.

Filter by label image


After clicking on the Lib: Cryptography label the issues have been filtered to show only the issues that have Lib: Cryptography added to them.

Notice that Lib: Cryptography is not the only label in the image below. If you wish to further reduce the number of presented issues then additional labels can be added in the same way.

Filtering issues by an app label image

Checking for available issues

It's important to check if anyone else is currently working on an particular issue to avoid performing duplicate work. Not only would this be frustrating but also be an inefficient use of time.

You can check whether someone is assigned to an issue by looking under the Assignee tab. If there is an icon, then someone is tasked with that issue. If there is no icon, then it's likely that no one is currently working on that issue and you're free to assign it to yourself or post a comment so that the author can assign you.

Filtering issues by an app label image

Issue summary

You can see the activity of an issue by looking at the number of comments. This doesn't really tell you much aside from that there is a discussion about what should be done.

Filtering issues by an app label image

Clicking on the issue near the bottom Sum Merkle Proof Verification we can see some information about the library with some information on what the library intends to implement and why it is needed.

Filtering issues by an app label image

Creating an Issue

If there is work that a project can benefit from then an issue can be filed via a template or a blank form.

We encourage the use of the provided templates because they guide a user into answering questions that we may have. The templates are not mandatory but they provide structure for answering questions like:

  • What steps can be taken to reproduce the issue?
  • What feature is missing and how would you like it to work?
  • Is the improvement an improvement or a personal nitpick?

Using issue templates image


The questions themselves are not that important, but what is important is providing as much detail about the task as possible. This allows other developers to come to a decision quickly and efficiently regarding the new issue.

Library Quality

The quality of a library can be determined by a variety of measures such as intended utility or adoption. The metric that the following sections will focus on is developer experience.

In the following sections we will take a look at:

If the library is well structured, tested and documented then the developer experience will be good.

Library Structure

In order to navigate through a library easily, there needs to be a structure that compartmentalizes concepts. This means that code is grouped together based on some concept.

Here is an example structure that we follow for Sway files in the src directory.

src/
├── lib.sw
└── my_library/
    ├── data_structures.sw
    ├── errors.sw
    ├── events.sw
    ├── my_library.sw
    └── utils.sw

In the example above there are no directories, however, it may make sense for a project to categorize concepts differently such as splitting the data_structures.sw into a directory containing individual modules.

data_structures.sw

Contains data structures written for your project.

  • structs
  • enums
  • trait implementations

errors.sw

Contains enums that are used in require(..., MyError::SomeError) statements. The enums are split into individual errors e.g. DepositError, OwnerError etc.

pub enum MoveError {
    OccupiedSquare: (),
    OutOfBounds: (),
}

events.sw

Contains structs definitions which are used inside log() statements.

pub struct WinnerEvent {
    player: Identity,
}

my_library.sw

This is the core of your library. It will host anything that is exposed for developers to use with an Application Binary Interface (ABI) as well as functions contracts or other libraries may call. You can think of this as the entry point which all other devs will use to interact with the library.

utils.sw

Any private functions (helper functions) that your contracts use inside their functions.

Code Structure

Structuring code in a way that is easy to navigate allows for a greater developer experience. In order to achieve this, there are some guidelines to consider.

  1. Fields in all structures should be alphabetical
  2. Functions should be declared by the weight of their purity e.g.
    1. read & write first
    2. read second
    3. pure last
  3. Structures should be grouped into modules and the content inside should be alphabetically ordered
  4. Dependencies and imports should be alphabetical
  5. Document the parameters of the interface in alphabetical order and preferably declare them in the same order in the function signature

An important aspect to remember is to use the formatter(s) to format the code prior to a commit.

  • cargo fmt to format Rust files
  • forc fmt to format Sway files

Documentation

Documentation is arguably the most important aspect of any open source project because it educates others about how the project works, how to use it, how to contribute etc.

Good documentation enables frictionless interaction with the project which in turn may lead to a greater userbase, including contributors, which causes a positive feedback loop.

In the following sections we will take a look at how to document the:

  • Read me
    • The first document a user will see which includes content such as installation instructions
  • Code
    • Documenting the code itself such that contributors know how to interact with it
  • Specification
    • Presenting technical (or non-technical) information about your project

Read me

The README.md is likely to be the first file that a user sees therefore from the perspective of a user there are certain expectations that need to be met.

Introduction

A user needs to know what the library is and what it does. The content in this section should be a brief overview of what the library can do and it should not touch on any technical aspects such as the implementation details.

Once a user has an idea of what they are getting into they can move onto the next section.

Quickstart

The quickstart should inform the user where the library is supported (e.g. the operating system), and has been tested to work, before moving onto the installation and removal instructions.

A user should be able to easily install, use, and potentially remove your library to create a good experience.

Miscellaneous

This "section" can be a number of sections which the authors of the library think the user may be interested in.

Some information may include:

  • Links to documents such as contributing guides, blogs, socials etc.
  • Ways to support the library
  • Known issues

There is a variety of content that may be added, however, it's important to note that this is the first document a user will see and thus should not be overloaded with information. If the user can learn a little about the library, use it, and find links to additional content then the document has achieved its purpose.

Code

Documenting code is an important skill to have because it conveys information to developers about the intention and usage of the library.

In the following sections we'll take a look at three ways of documenting code and a code style guide.

For general documentation refer to how Rust documents code.

ABI Documentation

ABI documentation refers to documenting the interface that another developer may be interested in using.

The form of documentation we focus on uses the /// syntax as we are interested in documenting the ABI functions.

In the following snippet, we provide a short description about the functions, the arguments they take, and when the calls will revert. Additional data may be added such as the structure of the return type, how to call the function, etc.

{{#include ../../../../code/connect_four/src/interface.sw:interface}}

In order to know what should be documented, the author of the code should put themselves in the position of a developer that knows nothing about the function and think about what sort of questions they may have.

Comments

Comments are used by developers for themselves or other developers to provide insight into some functionality.

There are many ways to achieve the same outcome for a line of code however there are implementation tradeoffs to consider and a developer might be interested in knowing why the current approach has been chosen.

Moreover, it may not be immediately clear why, or what, some line of code is doing so it may be a good idea to add a comment summarizing the intent behind the implementation.

The following snippet looks at two items being documented using the comment syntax //.

  • Item1 has poor comments that do not convey any meaningful information and it's better to not include them at all.
  • Item2 has taken the approach of describing the context in order to provide meaning behind each field
// This is bad. It's repeating the names of the fields which can be easily read
pub struct Item1 {
    /// Identifier
    id: u64,
    /// Quantity
    quantity: u64,
}

// This is better. It conveys the context of what the fields are
pub struct Item2 {
    /// Unique identifier used to retrieve the item from a vector of items held in storage
    id: u64,
    /// The number of remaining items left in production
    quantity: u64,
}

Naming Components

Documenting the interface and adding comments is important however the holy grail is writing code that is self-documenting.

Self-documenting code refers to code that written in such a way that a regular user who has never seen a line of code before could interpret what it is doing.

One of the most difficult aspects of programming is coming up with meaningful names that describe the content without being overly verbose while also not being too concise.

Naming components is both a skill and an art and there are many aspects to consider such as the context in which that variable may exist. In one context an abbreviated variable may be meaningful because of how fundamental that concept is while in another context it may be regarded as random characters.

Here are some points to consider when coming up with a name for a component.

Abbreviations

Abbreviating names is a bad practice because it relies on contextual knowledge of the subject. It forces the developer to step away from their task in order to find the definition of some abbreviation or perhaps wait on a response from another developer.

On the other hand, common abbreviations may be meaningful for a given context and it may be detrimental to come up with a different, or long form, name to describe the content.

In general, a developer should take a moment to consider if an abbreviation provides more benefit than cost and how other developers may interpret that name in the given context.

That being said, here are some examples that should be avoided.

Single Character Names

Using a single character to name a variable conveys little to no information to a developer.

  • Is this a throw away variable?
  • What is the variable meant to represent where ever it is used?
  • Does it make sense to call it by the chosen character e.g. x when referring to formulas?

Ambiguous Abbreviations

A common mistake is to abbreviate a variable when it does not need to be abbreviated or when the abbreviation may be ambiguous.

For example, in the context of an industry that deals with temperature sensors what does the variable temp refer to?

  • temperature
  • temporary
  • tempo

Perhaps in the specific function it makes sense to use the abbreviation. Nevertheless, it's better to add a few more characters to finish the variable name.

Declarative statements

When choosing a name, the name should be a statement from the developer and not a question. Statements provide a simple true or false dynamic while a variable that may be read as a question provides doubt to the intended functionality.

For example:

  • can_change -> authorized
    • The "can" can be read as a question or a statement.
    • Is the developer asking the reader whether something can change or are they asserting that something either is or is not authorized to change?
  • is_on -> enabled
    • "is" can also be read as a question posed to the reader rather than a simple declaration.

Style Guide

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

The following snippets present the style for writing Sway.

CapitalCase

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

struct MultiSignatureWallet {
    owner_count: u64,
}

trait MetaData {
    // code
}

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

snake_case

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

Module name:

library;

Function and variable:

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

SCREAMING_SNAKE_CASE

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

const MAXIMUM_DEPOSIT = 10;

Type Annotations

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

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;
}

Field Initialization Shorthand

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

The following struct has a field amount with type u64.

struct Structure {
    amount: u64,
}

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

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

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

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

Getters

Getters should not follow the pattern of get_XYZ() and instead should follow XYZ().

// Discouraged style
fn get_maximum_deposit() -> u64 {
    MAXIMUM_DEPOSIT
}

// Encouraged style
fn maximum_deposit() -> u64 {
    MAXIMUM_DEPOSIT
}

Specification

A specification is a document which outlines the requirements and design of the library. There are many ways to structure a specification and this number only grows when considering the industry and target audience.

For simplicity, a specification can be broken into two levels of detail and the one you choose depends on your target audience.

Non-technical specification

A non-technical specification is aimed at an audience that may not have the expertise in an area to appreciate the technical challenges involved in achieving the goals and thus it can be seen as an overview or summary.

As an example, this may be a developer explaining how a user would interact with the user interface in order to send a coin / asset to someone, without revealing the functionality behind signing a transaction and why a transaction may take some amount of time to be confirmed.

This type of specification is simple so that any layperson can follow the basic concepts of the workflow.

Technical specification

A technical specification is aimed at users that may be regarded as experts / knowledgeable in the area. This type of specification typically assumes the reader understands the basic concepts and dives into the technical aspects of how something works, step-by-step.


Note: Diagrams are a fantastic visual aid no matter the level of detail

Testing

Testing is a large topic to cover therefore this section will only cover some points that are followed in the repository.

File Separation

There are three components to the tests and they have the following structure.

tests/
└── src/
    └── my_library/
        ├── functions/
        |     └── 1 file per ABI function
        ├── utils/
        |     └── mod.rs
        └── harness.rs

functions

The functions directory contains 1 file per function declared in the ABI and all test cases (not utility / helper functions) for that function are contained within that module.

There are two possibilities with any function call and either the call succeeds or it reverts. For this reason each file is split into two sections:

  • success
  • revert

All of the tests where the function does not revert should be contained inside the success case while the reverting calls (panics) should be contained inside the revert module.

utils

The utils directory contains utility functions and abstractions which allow the tests in the functions directory to remain small, clean and "DRY" (do not repeat yourself).

This can be achieved by separating content into files, internally creating modules in mod.rs or a mixture of both.

The repository follows the pattern of putting utility functions in mod.rs and separating them internally into ABI wrappers and test helpers. The ABI wrappers are functions which directly call the contract function with the arguments passed in while the test helpers are general utility functions such as creating a new contract instance for a new test etc.

harness.rs

The harness file is the entry point for the tests, and thus it contains the functions and utils modules. This is what is executed when cargo test is run.

Testing Suggestions

Here are some tips on how to approach testing:

  • Similar to code structure content, each file should be ordered alphabetically, with one exception, so that it's easy to navigate
    • Test conditions in the order in which they may occur
      • If a test has multiple assertions then the first assertion should be tested first, second assertion second etc.
  • Check the code coverage
    • All assertions & requirements should be tested
    • Check boundary conditions to see if the values passed in work throughout the entire range
  • There should be positive and negative test cases meaning that a test should pass with correct data passed in but it should also revert when incorrect data is used
  • When writing a test that changes state the test should first assert the initial condition before performing some operation and then testing the outcome of that operation
    • If the initial condition is not proven to be what is expected then there is no guarantee that the operation has performed the correct behavior
    • This also means that the initial condition should be compared to the post condition
  • Comments should only be added to explain sections of each test if they provide insight into some complex behavior
    • If a function sets up the initial environment then there is no point in adding a comment "set up the environment" because the function name should be clear enough e.g. fn setup()
  • Any tests that are ignored should be documented in the test so that the reader knows why something is currently unimplemented
    • Do not leave in commented out tests. #[ignore] them
  • Unit tests should remain as unit tests
    • Do not bundle multiple different checks into one test unless it becomes semantically meaningless when separating them
  • Checking that a behavior continues to work more than once may be necessary at times
    • If a user can deposit then there should be a test to see that they can deposit more than once

Pull Requests

A pull request is a term used to identify when a piece of work is ready to be pulled into and merged with another piece of work. This functionality is especially useful when collaborating with others because it allows a review process to take place.

In order to create a high quality pull request there are a couple areas that need to be considered:

Committing your work

A commit can be thought of as a snapshot in time which captures the difference between the snapshot that is currently being made and the previous snapshot.

When creating a snapshot there are some points to consider in order to keep a high quality log:

  • The quantity of work between commits
  • Grouping work by task / concept
  • The message used to track the changes between commits

Quantity of Work

The amount of work done per commit is dependent upon the task that is being solved however there is a general rule to follow and that is to avoid the extremes of committing everything at once or committing every minor change such as a typo.

The reason for not committing all of the work at once is twofold:

  • When a fault occurs which leads to a loss of work then all of that work is lost
  • If a section of work needs to be reverted then everything must be reverted

Similarly, small commits should be avoided because:

  • A lot of commits may be considered as spam and may be difficult to parse

Categorization

Categorizing commits into issues being resolved allows us to easily scope the amount of work per commit. With appropriate categories the likelihood of too much, or not enough, work being committed is reduced.

An example could be a failing test suite which includes multiple functions that were re-written. In this instance it may be a good idea to fix a test, or a test suite, for one specific function and committing that work before moving onto the next.

This creates a clear separation within the task of fixing the test suites by fixing one suite in one commit and another in another commit.

Commit Messages

Once the issue has been resolved it's time to write a message that will distinguish this commit from any other.

The commit message should be a concise and accurate summary of the work done:

  • Good commit message:
    • Fixed precondition in withdraw() which allowed draining to occur
  • Bad commit message:
    • Fix
    • Fixed function
    • Fixed an assertion where a user is able to repeatedly call the withdraw() functions under an edge case which may lead to the contract being drained

More information about commit messages may be found in:

Creating a pull request

There are two types of pull requests and depending on which one is chosen it will convey a different intent to the authors.

A regular pull request is for when the author of the pull request is satisfied with the work done and believes that the author(s) of the library should perform a review in preparation of merging the work into some branch.

A draft pull request indicates that work is currently in progress and not ready for review.

When to create a pull request

There are two approaches that can be taken:

  • A pull request can be made when the task is deemed to be completed
  • A draft pull request can be created after the first commit in order to allow for easy tracking of the progress

Which one should be chosen may come down to preference or the contributing guide of a library. That being said, the benefit of creating and working on a draft is that it makes it easier to spot the request and thus early comments may be left which provide additional support.

How to structure a pull request

Depending on the account permissions and where the pull request is being made, there may be some features that are unavailable. For example, an external contributor may not be able to set a label to categorize the request.

There are at least five sections to consider when creating a pull request:

The Title

It's important to provide a title that accurately identifies the work that is being done. This is easy if there is an issue, even more so if the issue is described well, as the title can be directly copy and pasted from the issue. This allows for a one-to-one mapping of an issue to pull request which makes it easy to spot when an issue is ready to be merged.

The Description

The information in the pull request should be structured neatly through the use of headings, bullet points, screenshots etc. because this makes it easier to immediately see the changes rather than having to parse through one large paragraph.

Some ideas for sections are:

  • The changes that have been made and the motivation behind them
  • Limitations
  • Assumptions
  • Future work if the pull request is part of an epic (set of tasks / issues)

The Reviewers

If the library is managed well then a contributor does not have to think about who should review their work because it will be automatically filled in for them. This is done through the use of a code owners file.

If that is not the case then the contributor will need to figure out the correct author(s) for code review and select them (if permissions allow it) or the request will be without any reviews until an author spots the request and assigns someone.

The Labels

If there is an issue which is well managed then the labels for that issue can be set on the pull request (if permissions allow it) otherwise an author may need to set the labels if they choose to.

The Issues

If there is an issue that the pull request is working off of then it's a good practice to reference that issue so that it gets closed automatically when the pull request is merged. This can be done via the user interface or by reference in the description using a closing keyword.

For example, issue number 123 would be referenced in the description as closes #123.

Additionally, referencing the issue that the pull request is based on allows the reviewer to easily click on the link which will take them to the issue. This makes it easy to see the problem in detail and any discussion that occurred.

Merging the Pull Request

Once the request has received enough approvals from the authors then either the authors or the contributor may merge the work in. When attempting to merge there may be an option to squash the commits. It's a good idea to delete the previous commits in the optional description so that a single message summarizes the entire work that has been done. This makes it easier to parse the commit history.