Fuel, FuelVM and Sway Language
Fuel's Purpose-Built Architecture
Fuel introduces a novel architecture specifically optimized for Ethereum rollups. This architecture addresses the core issues by integrating:
Parallelization: FuelVM supports parallel transaction execution, leveraging modern multi-core processors to significantly enhance throughput and reduce latency.
State Minimization: Fuel utilizes a UTXO model to manage state efficiently, ensuring that the growth of blockchain state is controlled, thereby preserving decentralization.
Interoperability: Fuel’s design allows for seamless and secure interaction between different rollups, enabling a connected ecosystem of decentralized applications.
By focusing on these key areas, Fuel delivers a high-performance execution environment that meets the evolving needs of the Ethereum ecosystem.
Key Components of Fuel:
FuelVM : The core engine providing parallelization, state minimization, and customizability.
Enhanced Ethereum VM: FuelVM incorporates long-suggested improvements to the Ethereum Virtual Machine (EVM) that couldn't be implemented due to backward compatibility constraints.
Parallel Transaction Execution: It supports parallel transaction execution, enabling the use of multiple CPU threads and cores that are often idle in single-threaded blockchains.
UTXO Model: By using a strict state access list in the form of a UTXO model, FuelVM allows full nodes to identify transaction dependencies before execution, significantly boosting compute, state access, and transactional throughput.
Sway Language : A blockchain-specific language combining Solidity and Rust's strengths for high-performance dApps.
Developer Experience: Fuel introduces Sway, a domain-specific language (DSL) designed for a superior developer experience.
Based on Rust: Sway is based on Rust, incorporating its safety-first semantics and compile-time analysis features.
Smart Contract Paradigm: From Solidity, Sway adopts the concept of a smart contract language with built-in top-level contract storage and blockchain mechanisms, ensuring ergonomic and safe contract programming.
Static Auditing: Sway supports static auditing for smart contracts, enhancing safety and reliability.
Performance and Optimization: Sway is highly performant with extensible optimization passes and a modular backend, capable of targeting various blockchain architectures.
Fuel Token Bridge
Seamless L1-L2 Communication: The Fuel Token Bridge enables smooth and secure interaction between Ethereum (L1) and Fuel (L2), facilitating asset transfers and data exchanges across these layers.
Advanced Message Protocol: The bridge operates with a robust protocol that ensures the integrity and efficiency of transactions, utilizing concepts like UTXOs, epochs, and finality.
Cross-Layer Integration: By managing the complexities of cross-layer communication, the Fuel Token Bridge enhances the interoperability and scalability of blockchain applications.
Sway Libraries
Extending Functionality: Sway Libraries provide a collection of utilities and helper functions that extend the capabilities of Sway beyond the standard library, aiding developers in blockchain development.
Variety of Libraries: These include libraries for convenience functions, standards support, data types, security functionalities, and more, ensuring developers have the tools they need for robust contract development.
Asset Libraries: Focused on Native Assets, these libraries provide helper functions for working with standards like SRC-20, SRC-3, and SRC-7 on the Fuel Network.
Access Control and Security: Sway includes libraries like the Ownership, Admin, Pausable, and Reentrancy Guard libraries, which enhance the security of smart contracts by providing mechanisms for access control and protection against attacks.
Cryptography Libraries: These libraries extend cryptographic functionalities beyond what the standard library offers, including tools like the Bytecode and Merkle Libraries for on-chain verification and off-chain computation verification.
Math and Data Structures: The Sway Libraries also include specialized libraries for mathematical operations and complex data structures, such as the Fixed Point Number Library, Signed Integers, and the Queue library, which enable advanced functionality in smart contracts.
Forc Toolchain: Tools for easily building and deploying dApps on Fuel chains.
Key Differences between EVM and FuelVM Assets
ERC-20 vs Native Asset
On the EVM, Ether is the native asset. As such, sending ETH to an address or contract is an operation built into the EVM, meaning it doesn't rely on the existence of a smart contract to update balances to track ownership as with ERC-20 tokens.
On the FuelVM, all assets are native and the process for sending any native asset is the same.
ERC-721 vs Native Asset
On the EVM, an ERC-721 token or NFT is a contract that contains multiple tokens which are non-fungible with one another.
On the FuelVM, the ERC-721 equivalent is a Native Asset where each asset has a supply of one. This is defined in the SRC-20; Native Asset Standard under the Non-Fungible Asset Restrictions.
No Token Approvals
An advantage Native Assets bring is that there is no need for token approvals; as with Ether on the EVM. With millions of dollars hacked every year due to misused token approvals, the FuelVM eliminates this attack vector.
Example of SRC-20 Implementation
Sway :
Sway is designed to help developers write efficient and high-performance smart contracts tailored to Fuel's architecture. In addition to the language, Fuel provides a robust developer environment, including custom-built tools like Forc and Fuelup, which streamline the development process for applications running on the Fuel network.
Sway Program Types
A Sway program has a specific type: contract, predicate, script, or library. The first three types are deployable to the blockchain, while a library is designed for code reuse and is never directly deployed. Each Sway file must declare its program type, and a project can include multiple libraries but only one contract, script, or predicate. Scripts and predicates require main functions as entry points, while contracts publish an ABI.
Contracts: Used for protocols or systems operating within fixed rules, such as staking contracts or decentralized exchanges (DEX).
Contracts have an associated ContractId type in Sway. The ContractId type allows for Sway programs to refer to contracts in the Sway language.
Contracts are similar to Scripts or Predicates at the language level. The main feature that distinguishes smart contracts from scripts or predicates is that they are callable and stateful.
Scripts: Facilitate complex on-chain interactions that are temporary, like creating a leveraged position through a series of transactions on a DEX and Lender.
script;
use interfaces::{data_structures::{Asset, PoolId}, mira_amm::MiraAMM};
use math::pool_math::get_deposit_amounts;
use utils::blockchain_utils::check_deadline;
use std::asset::transfer;
configurable {
AMM_CONTRACT_ID: ContractId = ContractId::from(0x0000000000000000000000000000000000000000000000000000000000000000),
}
fn main(
pool_id: PoolId,
amount_0_desired: u64,
amount_1_desired: u64,
amount_0_min: u64,
amount_1_min: u64,
recipient: Identity,
deadline: u32,
) -> Asset {
//E ensure deadline hasn't pass
check_deadline(deadline);
let amm = abi(MiraAMM, AMM_CONTRACT_ID.into());
//E fetch pool metadata
let pool_meta_opt = amm.pool_metadata(pool_id);
require(pool_meta_opt.is_some(), "Pool doesn't exist");
let pool_meta = pool_meta_opt.unwrap();
let (amount_0, amount_1) = get_deposit_amounts(
amount_0_desired,
amount_1_desired,
amount_0_min,
amount_1_min,
pool_meta
.reserve_0,
pool_meta
.reserve_1,
);
transfer(Identity::ContractId(AMM_CONTRACT_ID), pool_id.0, amount_0);
transfer(Identity::ContractId(AMM_CONTRACT_ID), pool_id.1, amount_1);
amm.mint(pool_id, recipient)
}
Libraries: Contain reusable code for common tasks, such as fixed-point or big number math.
Predicates : From the perspective of Sway, predicates are programs that return a Boolean value and which represent ownership of some resource upon execution to true. They have no access to contract storage. Any assets sent to a predicates returning true can be unlocked or claimed by executing it, as it always evaluates to true.
Good to know about Sway :
Standards : Similar to ERC standards for Ethereum and Solidity, Sway has its own SRC standards that help enable cross compatibility across different smart contracts. For more information on using a Sway Standard, you can check out the Sway-Standards Repository.
Libraries : the standard library, also referred to as std, is a library that offers core functions and helpers for developing in Sway. The standard library has its own reference documentation that has detailed information about each module in std.
How to use it : use std::storage::storage_vec::*;
Variables in Sway are immutable **by default. This means that, by default, once a variable is declared, its value cannot change. This is one of the ways Sway encourages safety.
There is no concept of msg.sender, but there is a msg_sender() function, which is similar to msg.sender and is part of the Sway standard library.
Sway has the keyword storage, which is similar to a global storage module. All variables that need to be stored persistently in the contract has to be defined.
Functions do not have public, private, or other access controls, but they do have the concept of Purity. Functions are pure by default but can opt into impurity via the storage function attribute. The storage attribute may take read and/or write arguments indicating which type of access the function requires
#[storage(read)]
fn get_amount() -> u64 {
...
}
#[storage(read, write)]
fn increment_amount(increment: u64) -> u64 {
...
}
fn a_pure_function() {
...
}
#[storage()]
fn also_a_pure_function() {
...
}
No Integer Overflow
No Delegate Call (for now, only LDC) and no Self-Destruct
Sway provides unit testing, so you can test your Sway code with Sway. You can also use the Fuel Rust SDK or TypeScript SDK to test your Sway programs.
You can use the Solidity to Sway transpiler built in to the Sway Playground to convert Solidity code into Sway code. Note that the transpiler is still experimental, and may not work in every case.
Fuel ToolChain
Forc(forc)
The "Fuel Orchestrator" Forc is the equivalent of Rust's Cargo. It is the primary entry point for creating, building, testing, and deploying Sway projects.
Sway Language Server (forc-lsp)
The Sway Language Server forc-lsp is provided to expose features to IDEs. Installation instructions.
Sway Formatter (forc-fmt)
A canonical formatter is provided with forc-fmt. Installation instructions. It can be run manually with forc fmt
The Visual Studio Code plugin will automatically format Sway files with forc-fmt on save, though you might have to explicitly set the Sway plugin as the default formatter, like this:
"[sway]": {
"editor.defaultFormatter": "FuelLabs.sway-vscode-plugin"
}
Fuel Core (fuel-core)
An implementation of the Fuel protocol, Fuel Core, is provided together with the Sway toolchain to form the Fuel toolchain. The Rust SDK will automatically start and stop an instance of the node during tests, so there is no need to manually run a node unless using Forc directly without the SDK.
Forc Project :
forc new my-fuel-project
forc build --asm final
Language Specs
- Variables
Variables in Sway are immutable by default, but can be made mutable using mut
// Immutable variable
let foo = 5;
// Mutable variable
let mut foo = 5;
foo = 6;
- Built-in Types
Sway has the following primitive types:
() (unit type)
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)
str[] (fixed-length string)
str (string slices)
bool (Boolean true or false)
b256 (256 bits (32 bytes), i.e. a hash)
address Unlike the EVM, an address never refers to a deployed smart contract (see the ContractId type below). An Address can be either the hash of a public key (effectively an externally owned account if you're coming from the EVM) or the hash of a predicate. Addresses own UTXOs.
pub struct Address {
value: b256,
}
// types
let my_number: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
let my_address: Address = Address::from(my_number);
let forty_two: b256 = my_address.into();
ContractId Type : The ContractId type is a type-safe wrapper around the primitive b256 type. A contract's ID is a unique, deterministic identifier analogous to a contract's address in the EVM. Contracts cannot own UTXOs but can own assets.
Identity Type : 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.
A common use case for Identity is for access control. The use of Identity uniquely allows both ContractId and Address to have access control inclusively.
let sender = msg_sender().unwrap();
require(
sender == storage
.owner
.read(),
MyError::UnauthorizedUser(sender),
);
Unit Type : The unit type, (), is a type that allows only one value, and thus, represents a value with no information. It is used to indicate the absence of a meaningful value, or the result of a function that performs an action, but does not return any data. Unit type in Sway serves a similar purpose as void in imperative languages like C or Java.
Casting : casting using try_from method will only fail if the result is too large for casted type u64::try_from(numerator_1.as_u256() * numerator_2.as_u256() / denominator.as_u256()).unwrap()
- Functions
Functions in Sway are declared using the fn keyword.
fn add(a: u64, b: u64) -> u64 {
a + b
}
- Structs, Tuples, and Enums
Sway supports struct types for grouping related data, tuples for fixed-size collections of multiple types, and enums for defining types that can be one of several variants.
struct Point {
x: u64,
y: u64,
}
let p = Point { x: 10, y: 20 };
enum Color {
Red: (),
Green: (),
Blue: (),
}
let color = Color::Red;
- Methods and Associated Functions
Methods and associated functions are defined within impl blocks.
Methods are similar to functions in that we declare them with the fn keyword and they have parameters and return a value. However, unlike functions, Methods are defined within the context of a struct (or enum), and either refers to that type or mutates it. The first parameter of a method is always self, which represents the instance of the struct (or enum) the method is being called on.
struct Foo {
bar: u64,
baz: bool,
}
impl Foo {
fn is_baz_true(self) -> bool {
self.baz
}
fn new(bar: u64, baz: bool) -> Foo {
Foo { bar, baz }
}
}
- Constants
Constants are immutable values that are evaluated at compile time.
const MAX_LIMIT: u64 = 100;
- Logging
The logging library provides a generic log function that can be imported using use std::logging::log and used to log variables of any type. Each call to log appends a receipt to the list of receipts. There are two types of receipts that a log can generate: Log and LogData.
fn log_values(){
// Generates a Log receipt
log(42);
// Generates a LogData receipt
let string = "sway";
log(string);
}
- Result<T, E> ; Option<T>
- Result is the type used for returning and propagating errors. It is an enum with two variants: Ok(T), representing success and containing a value, and Err(E), representing error and containing an error value. The T and E in this definition are type parameters, allowing Result to be generic and to be used with any types.
/// Result is a type that represents either success (Ok) or failure (Err).
pub enum Result<T, E> {
/// Contains the success value.
Ok: T,
/// Contains the error value.
Err: E,
}
Functions return Result whenever errors are expected and recoverable.
Type Option represents an optional value: every Option is either Some and contains a value, or None, and does not. Option types are very common in Sway code, as they have a number of uses:
Initial values where None can be used as an initializer.
Return value for otherwise reporting simple errors, where None is returned on error.
The implementation of Option matches on the variant: if it's Ok it returns the inner value, if it's None, it reverts.
/// A type that represents an optional value, either `Some(val)` or `None`.
pub enum Option<T> {
/// No value.
None: (),
/// Some value of type `T`.
Some: T,
}
Option is commonly paired with pattern matching to query the presence of a value and take action, allowing developers to choose how to handle the None case.
- Control Flow
Sway supports standard control flow constructs like if, else, match, while, and for.
fn main() {
let number = 6;
if number % 2 == 0 {
// Even number
} else {
// Odd number
}
let result = match number {
1 => "one",
2 => "two",
_ => "other",
};
}
Sway combines modern programming practices with blockchain-specific optimizations, providing a robust, secure, and efficient language for smart contract development. With its clear structure and support for various types and control flow mechanisms, Sway ensures developers can create tailored solutions for diverse blockchain applications.
- Storage Accesses
Declaring variables in storage requires a storage block that contains a list of all your variables, their types, and their initial values. The initial value can be any expression that can be evaluated to a constant during compilation, as follows:
storage {
var1: u64 = 1,
var2: b256 = b256::zero(),
var3: Address = Address::zero(),
var4: Option<u8> = None,
}
To write and read into a storage variable, you need to use the storage
- Token transfer
Native transfers is enabled for all tokens , example :
// FUNCTION CALL
let (amount_0, amount_1) = amm.burn
{
asset_id: lp_asset_id.into(), //E transfer lp_asset
coins: liquidity, //E amount = liquidity
}
(pool_id, recipient); //E parameters of the function
// FUNCTION DEFINITION
#[payable]
#[storage(read, write)]
fn burn(pool_id: PoolId, to: Identity) -> (u64, u64) {
validate_pool_id(pool_id);
let burned_liquidity = Asset::new(msg_asset_id(), msg_amount());
// ... //
}
- Others
Calling contracts : Smart contracts can be called by other contracts or scripts. In the FuelVM, this is done primarily with the call instruction.
LDC opcode : instruction allowing to load code into your code ⇒ not 100% like delegateCall
- Example Sway Code :
You can find example applications built with Sway in the Sway Applications repository on GitHub. You can also find projects building on Fuel in the Fuel ecosystem home
Security Risk of Sway Contracts
- Same Contract ID and Different Asset ID
Unlike the EVM, the contract address in FuelVM is not bound to a token. One contract address may have multiple assets, increasing security risks. When users transfer funds, they need to pay attention not only to the contract address but also to ensure the asset ID is correct.
- Same Asset ID and Different Contract ID
In FuelVM, the Asset ID determines the uniqueness of the asset, and a contract can have multiple assets, which is very different from the EVM. An attacker can create the same Asset ID through brute force:
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
The Asset ID is determined by the contract ID and sub ID, allowing an attacker to create a malicious contract and brute force the sub ID to match the target Asset ID. If the victim only sees that the Asset ID is the same and does not notice the inconsistent contract ID, they may be deceived.
Sway-Analyzer (Sway sliter equivalent)
Links : Sway Analyzer [ Blog Post, YT Video]
A security-focused static analyzer for Sway written in Rust. The tool makes use of the existing sway-ast and sway-parse crates in order to parse Sway source code into its abstract syntax tree (AST). A recursive AST visitor is implemented on top of this, which will walk the AST structures top-down in a context-sensitive manner. Detectors leverage the AST visitor in order to implement their logic by inspecting the values contained in certain parts of the AST structures.
Resources :