Rust-like lang.
cargo install forc fuel-core
Four types of sway programs
- contract
- predicate
- script
- library
File extension *.sw
, tests in *.rs
.
// 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;
}
}
#[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);
}
forc build --print-finalized-asm
std::address::Address
:b256
representing wallet addressstd::contract_id::ContractId
:b256
representing Contract IDstd::identity::Identity
: enum:Address
orContractId
std::vec::Vec
: heap-alloc vectorstd::option::Option
: enum with presence or absence of valuestd::result::Result
: enum for funcs that succeed or failstd::assert::assert
: func that reverts the VM if condition is falsestd::revert::require
: func that reverts the VM with msg if condition is falsestd::revert::revert
: func that reverts the VM
Primarily for protocols or systems that operate within a fixed set of rules. Callable, stateful, deployed to the chain as bytecode.
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;`
impl Wallet for Contract {
#[storage(read, write)]
fn receive_funds() {
// ...
}
#[storage(read, write)]
fn send_funds(amount_to_send: u64, recipient_address: Address) {
// ...
}
}
Reusable code for handling common situations.
// `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 {
// ...
}
// ...
}
use std::storage::{get, store};
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
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 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
}
// -------------------------------------------------------------------------------------------------
// 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,
}
- Native Asserts: Fuel VM can forward any native asset, not just base.
- No data serialization: Fuel VM has global shared memory, simply pass a pointer to it.
// 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
}
}