Skip to content

Instantly share code, notes, and snippets.

@jtriley-eth
Created October 28, 2022 19:14
Show Gist options
  • Save jtriley-eth/03fb11dc663d4bd4bf1c9aed97e3c218 to your computer and use it in GitHub Desktop.
Save jtriley-eth/03fb11dc663d4bd4bf1c9aed97e3c218 to your computer and use it in GitHub Desktop.

Sway Language

Rust-like lang.

Install

cargo install forc fuel-core

Overview

Four types of sway programs

  • contract
  • predicate
  • script
  • library

File extension *.sw, tests in *.rs.

Contract Example

// PROGRAM TYPE
contract;

// STORAGE DEFINITION
storage {
    // defines counter storage value
    counter: u64 = 0;
}

// ABI
abi Counter {
    // function can read and write storage
    #[storage(read, write)]
    // funciton definition
    fn increment();

    // function can read storage
    #[storage(read)]
    // function definition
    fn counter() -> u64;
}

// IMPL
impl Counter for Contract {
    #[storage(read)]
    fn counter() -> u64 {
        // returns counter value from storage
        return storage.counter;
    }

    #[storage(read, write)]
    fn increment() {
        // sets counter value in storage to itself plus one
        storage.counter = storage.counter + 1;
    }
}

Test Example

#[tokio::test]
async fn can_get_contract_instance() {
    // increment counter
    let _result = instance.increment().call().await.unwrap();

    // get counter value
    let result = instance.counter().call().await.unwrap();
    assert!(result.value > 0);
}

Show Assembly

forc build --print-finalized-asm

Std Library

  • std::address::Address: b256 representing wallet address
  • std::contract_id::ContractId: b256 representing Contract ID
  • std::identity::Identity: enum: Address or ContractId
  • std::vec::Vec: heap-alloc vector
  • std::option::Option: enum with presence or absence of value
  • std::result::Result: enum for funcs that succeed or fail
  • std::assert::assert: func that reverts the VM if condition is false
  • std::revert::require: func that reverts the VM with msg if condition is false
  • std::revert::revert: func that reverts the VM

Program Types

Contracts

Primarily for protocols or systems that operate within a fixed set of rules. Callable, stateful, deployed to the chain as bytecode.

ABI

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

    #[storage(read, write)]
    fn send_funds(amount_to_send: u64, recipient_address: Address);
}

// importable as:
// `use wallet_abi::Wallet;`

Implementing ABI for Smart Contract

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

    #[storage(read, write)]
    fn send_funds(amount_to_send: u64, recipient_address: Address) {
        // ...
    }
}

Libraries

Reusable code for handling common situations.

Writing libraries

// `src/lib.sw`

// dependencies (adjacent files).
dep block;
dep storage;
dep constants;

// indicates library is called `option`.
library option;

pub enum Option<T> {
    // ...
}

impl<T> Option<T> {
    fn is_some(self) -> bool {
        // ...
    }

    // ...
}

Using Libraries

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

Scripts

Used for complex on-chain interactions that don't persist. Needs a main entry point. Cannot be calle dby a contract, it only executes bytecode on the chain once to perform a task. They are state aware, but not stateful.

main is the entry point

Calling Smart Contract From Script

script;

use std::constants::ZERO_B256;
use wallet_abi::Wallet;

fn main() {
    let contract_address = /* ... */;
    let caller = abi(Wallet, contract_address);
    let amount_to_send = 200;
    let recipient_address = ~Address::from(/* ... */);
    caller.send_funds {
        gas: 10000,
        coins: 0,
        asset_id: ZERO_B256,
    }(amount_to_send, recipient_address);
}

Predicates

Predicates are a program that return a boolean and which represent ownership of a resource upon execution to true. No side effects.

predicate;

fn main() -> bool {
    true
}

Sway Syntax

Syntax

// -------------------------------------------------------------------------------------------------
// VARIABLES

// immutable, value is 4, type of u64 is inferred.
let foo = 5;

// same as above, but mutable.
let mut foo = 5;
foo = 6;

// type annotationed to u32
let foo: u32 = 5;

// statically allocated string
let bar: str[4] = "sway";

// configure-time constants.
// ------------
// `Forc.toml`
// [constants]
// some_contract_addy = { type = "b256", value = "..." }
// some_num = { type = "u64", value = "42" }
// ------------

// using consants.
let addr = some_contract_addy;
let num = some_num;

// -------------------------------------------------------------------------------------------------
// TYPES

// primitive:
// u8, u16, u32, u64, str[n], bool, b256

// numeric literal formats: hex, bin, dec, underscore delineated

// compound:
// arrays, tuples

// arrays always statically sized.

// blockchain types:
// Address, ContractId, Identity

// examples:

let n: u8 = 1;
let s: str[4] = "sway";
let b: bool = true;
let t: (u64, u64) = (1, 2);
let a: [u8; 5] = [1, 2, 3, 4, 5];

let beeg_num: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A
let addy: Address = ~Address::from(beeg_num);
let cont: ContractId = ~ContractId::from(beeg_num);
let id: Identity = Identity::Address(~Address::from(beeg_num));

// -------------------------------------------------------------------------------------------------
// FUNCTIONS

// declaration
fn equals(first_param: u64, second_param: u64) -> bool {
    // no `;` implicitly returns
    first_param == second_param
}

// mutable args. MUST use `ref` and `mut`
fn increment(ref mut num: u32) {
    let prev = num;
    num = prev + 1u32;
}

// -------------------------------------------------------------------------------------------------
// STRUCTS, TUPLES, ENUMS

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

impl Point {
    fn is_origin(self) -> bool {
        self.x == 0 && self.y == 0
    }

    fn update(ref mut self, x: u64, y: u64) {
        self.x = x;
        self.y = y;
    }
}

let stru = Point {
    x: 1,
    y: 2,
};

// tuple
let tup: (u8, bool, u64) = (1, true, 2);

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

let enu: Color = Color::Blue;

// -------------------------------------------------------------------------------------------------
// LOGGING

use std::logging::log;

let x = 42;

log(x);

// example log:
// "Log": {
//   "id": "0000000000000000000000000000000000000000000000000000000000000000",
//   "is": 10352,
//   "pc": 10404,
//   "ra": 42,
//   "rb": 1018205,
//   "rc": 0,
//   "rd": 0
// }

// -------------------------------------------------------------------------------------------------
// CONTROL FLOW

// conditional
if condition0 {
    // ...
} else if condition1 {
    // ...
} else {
    // ...
}

let a = if condition { 1 } else { 2 }

// match
let b = match a {
    1 => 2,
    2 => 4,
    _ => 420,
};

// loops (while, break, continue)
while true {
    if condition0 {
        break
    }

    if condition1 {
        continue
    }

    // ...
}

// -------------------------------------------------------------------------------------------------
// BLOCKCHAIN STUFFS

use core::num::*;
use std::{
    b512::B512,
    ecr::{
        ec_recover,
        ec_recover_address,
        EcRecoverError
    },
    logging::log,
    hash::{
        keccak256,
        sha256
    }
};

// hash
let digest = sha256(1);

// ecdsa recovery
let hi = /* ... */;
let lo = /* ... */;
let sig: B512 = ~B512::from(hi, lo);

// recovery of evm address:
// `std::vm::evm`

// -------------------------------------------------------------------------------------------------
// STORAGE

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

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

storage {
    var1: Type1 = Type1 { x: 0, y: 0 },
    map: StorageMap<u64, u64> = StorageMap {},
}

#[storage(write)]
fn store_something() {
    storage.var1.x = 1;
    storage.var1.y = 2;
    storage.map.insert(1, 2);
}

#[storage(read)]
fn get_something(key: u64) -> (u64, u64, u64) {
    (
        storage.var1.x,
        storage.var1.y,
        storage.map.get(key)
    )
}

const STORAGE_KEY: b256 = 0x0000000000000000000000000000000000000000000000000000000000000000;

#[storage(write)]
fn arbitrary_storage_write(x: u64) {
    store(STORAGE_KEY, x);
}

#[storage(read)]
fn arbitrary_storage_read() -> u64 {
    let value = get::<u64>(STORAGE_KEY);
    value
}

// -------------------------------------------------------------------------------------------------
// EVM STUFFS
use std::{
    chain::auth::{
        AuthError,
        msg_sender,
    },
    reentrancy::reentrancy_guard,
}

EVM vs FuelVM

  1. Native Asserts: Fuel VM can forward any native asset, not just base.
  2. No data serialization: Fuel VM has global shared memory, simply pass a pointer to it.

Inline Assembly

// inline assembly function, take u32, returns u32
pub fn add_1(num: u32) -> u32 {

    // declare asm block with register names to operate on passed as args
    asm(r1: num, r2) {
        // add `r1` and `one`, store the result in `r2`
        add r2 r1 one;
        // cast `r2` to u32
        r2: u32
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment