Sway vs. Solidity Part 1: What’s The Difference?
In being one of the first protocols to launch on Fuel Network, the Poolshark Protocol will be written in Sway, the native language designed specifically for the FuelVM.
It is heavily inspired by Rust makes and quite a few considerations regarding how to bring superior performance and flexibility to the web3 experience. It is UTXO-based instead of account-based like Solidity.
Many of those interested in Fuel Network will first want to know what makes Sway different and the concepts they need to get up to speed with in order to develop for the FuelVM.
If you missed our article on the core differences between the FuelVM and the EVM, we recommend visiting that first.
If you’d like to follow along with this article series and install the Fuel toolchain, you can visit the installation page in the Sway book.
Let’s dive right into it 🌊!
Sway Core Concepts
Before deep-diving here is a short list of the concepts we’re going to cover:
- Importing dependencies
- Contract storage
- Public functions
- Storage maps
- Require statements
Below is a sample NFT contract called main.sw
that we will reference:
contract;use std::{
storage::StorageMap,
logging::log
};struct MintEvent {
owner: Identity,
total_tokens: u64,
}storage {
total_supply: u64 = 0,
max_supply: u64 = 0,
balances: StorageMap<Identity, u64> = StorageMap {},
}abi NFT { #[storage(read)]
fn balance_of(owner: Identity) -> u64; #[storage(read)]
fn max_supply() -> u64; #[storage(read,write)]
fn mint(amount: u64, to: Identity);
}impl NFT for Contract { #[storage(read)]
fn max_supply() -> u64 {
storage.max_supply
} #[storage(read)]
fn balance_of(owner: Identity) -> u64 {
storage.balances.get(owner)
} #[storage(read, write)]
fn mint(amount: u64, to: Identity) { storage.balances.insert(to, storage.balances.get(to) + amount); storage.total_supply += amount; assert(storage.total_supply <= storage.max_supply); log(MintEvent {
owner: to,
total_tokens: amount,
}); }
}
The above is a basic implementation of an NFT contract, where users can call mint
to increase their balance or call balance_of
to check their balances.
If you would like to see a full version of an NFT implementation, you can jump to the sway applications Github repo.
We recommend reading through the rest of this article to ensure you understand the Sway basics first. Let’s go through the code line-by-line.
Declaring the Program Type
contract;
This line labels this file as the contract
byte code type. The key features of a contract are that it is both callable and stateful.
We recommend checking the Program Types page if you would like to explore the other program typesProgram Types page.
Importing Dependencies
dep interface;
The dep
keyword is used to import code from other libraries before the mention of the use
keyword.
This will make all the other items, namely functions and structs, accessible from our contract.
Here will will implement the NFT
ABI, which is a fancy way to refer to the set of functions that our contract will implement.
use std::{
chain::auth::msg_sender
storage::StorageMap };
use
blocks declare what exact items will see usage from a given library, which could be structs, functions, or other public entities.
Within the std
library, which you can find here, the msg_sender
function is located with src/chain/auth.sw
.
use
blocks will always be of the pattern [lib_name_and_path]::[function_or_struct_name]
. As seen here, the lib path portion can have multiple levels.
Sway book reference: Writing Libraries
Declaring Storage
“The English never abolish anything. They put it in cold storage.”
storage {
total_supply: u64,
max_supply: u64,
balances: StorageMap<Identity, u64> = StorageMap {},
}
In Solidity, global variables declared outside function braces are treated as contract storage. These values are used outside of contract calls to aggregate values to be used for future calls.
In Sway, you declare all the storage variables for your contract inside a storage
block.
Since the FuelVM only supports up to u64
, this is used as the standard size for all token supply values.
A u64
simply represents a 64-bit unsigned integer. If as a developer you’d like to use a signed value, I recommend taking a look at our I24 implementation being used in our AMM library.
Sway book reference: Contract Storage
The StorageMap Type
The StorageMap
type is the equivalent of the mapping
type used in Solidity. It is intended for storing key-value pairs which are typically used for ERC-20 balances.
In this example, the key
would be the address
of the user, with the value
being the user’s token balance.
In Sway, we have the Identity
type, which could be either a ContractId
(i.e. another smart contract) or Address
.
To interact with the StorageMap
, we use both the get()
and insert()
methods to retrieve values or add/update values in the map.
Sway book reference: Storage Maps
Implementing a Contract ABI
impl NFT for Contract {
Here we are implementing the NFT
ABI which we imported from the interface
. This ABI will simply be defined as such:
library interface;abi NFT {
#[storage(read)]
fn total_supply() -> u64; #[storage(read)]
fn max_supply() -> u64; #[storage(read,write)]
fn mint() -> mint(amount: u64, to: Identity)
}
This will allow any connecting contracts or providers making external calls to interact with your contract.
Sway book reference: Writing a Smart Contract
Writing Methods
It’s not the Method. It’s the Mindset.
Methods in Sway that either read from or write to storage will require #[storage(read,write)]
permissions depending on the level of access required for the method.
The
max_supply
Method
#[storage(read)]
fn max_supply() -> u64 {
storage.max_supply
}
Since max_supply
is a u64
type we will set u64
as the return type (denoted by using -> u64
).
The storage
keyword allows us to access anything declared in the storage
block.
The last line of our method will also be the return value, so there is no need for a return
statement or a ;
at the end of this line.
The
total_supply
Method
#[storage(read)]
fn balance_of(owner: Identity) -> u64 {
storage.balances.get(owner)
}
The StorageMap
declared in our storage
block has keys of type Identity
, which can either resolve to a ContractId
for a contract or Address
for an address tied to a user key pair.
We call the get
method on the balances
StorageMap
and pass the owner
key to retrieve the balance for this Identity
.
Again since this is our return value there is no need for a return
statement nor a ;
to complete our function body.
The
mint
Method
#[storage(read, write)]
fn mint(amount: u64, to: Identity) { storage.balances.insert(to, storage.balances.get(to) + amount);
storage.total_supply += amount; assert(storage.total_supply <= storage.max_supply);
log(MintEvent {
owner: to,
tokens_minted: amount,
});
}
Inside the mint
method, we will modify storage variables and thus the permission of #[storage(read, write)]
is required.
Next, we will assign an additional balance of the amount
to the existing balance inside the storage.balances
map.
The insert()
method will insert or update values in our StorageMap
. It takes the parameters key and value. The key will be used for future lookups to access the value inserted.
Next, we update total_supply
by adding the amount we just minted to the user.
We then use assert()
, which Solidity developers may recognize as similar to a require
statement, to make sure we are not exceeding the max_supply
in which case the transaction would revert.
Lastly, we use the log
method to log the MintEvent
similar to the emit
keyword in Solidity, exposing the owner
and how many NFTs they minted.
Part 2: Compiling and Deploying
In the second part of this series, we will cover the process for compiling and deploying your contracts, taking you through the entire installation process as well as the contract deployment.
Hopefully you found this article insightful and now have some basic ideas about what Smart Contract Development looks like in Sway.
Feel free to join the Fuel Discord if you have any questions relating to developing in Sway and the Poolshark Discord if you have any questions relating to this article or the Decentralized Exchange we are building. See you in the next one!
Part 1 Complete ✅