## Rust And NEAR: An Introduction
Here is a todo list for you to follow.
## ToDo List:
- [ ] ToDos
- [ ] Download and Install Rustup and Cargo
- [ ] Explore Rusts and Test
- [ ] Explore NEAR
- [ ] Explore NEAR-SDK
- [ ] Explore NFTs using NEAR
### Rust Setup:
For the purpose of this introduction to Rust and NEAR I am assuming that you have the basic setup of Rust complete on your machine, and you can build and run, a hello world program through Cargo.If not, you can install `rustup` from [here](https://www.Rust-lang.org/tools/install "Rust Installation"). Let's get started.
### Outline:
1. Cargo Introduction.
2. Rust Syntax. (Functions, Variable, Data_Types).
3. Rust Tests.
4. NEAR Setup.
5. Introduction to NEAR SDK.
6. Basic Contract.
7. NFT.
8. NFT Using NEAR.
#### Cargo Introduction
Cargo is Rust's package manager. It provides an interface to build, run and test Rust programs. It makes the life of Rust developer a lot easier when it comes to package management and adding dependencies to your project. You will see that in a while when adding the NEAR SDK to your project. It is always better you take a look at Rust [crates registry](https://crates.io/) before trying to implement things from scratch.
To start a new project, for an executable, all you have to do is run
```console
Cargo new [project_name]
```
In our case the project name will be `intro_to_rust_near`. Some other useful Cargo commands are as follows.
```console
Cargo run
Cargo build
Cargo test
```
These are used to run, build and test our project, we will go through some of the extra flags we can add when we need them. Cargo uses a file called `Cargo.toml`(Tom's Obvious Minimal Language.). To manage dependencies, versioning and if the crate is library or an executable.
A basic toml file when you run `Cargo new` looks like this.
```toml
[package]
name = "intro_to_rust_near"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.Rust-lang.org/Cargo/reference/manifest.html
[dependencies]
```
Let's suppose we want to add a [dependency](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/ "NEAR-SDK") we will just add:
```toml
[dependencies]
NEAR-SDK = "3.1.0"
```
And we can use the interface provided by the NEAR SDK in our program and Cargo will manage the rest. Other than package and dependencies, you might also see other headers in the TOML file like, `lib`, that specifies the crate we are going to write is a library.
**Let's Summarise:**
* Cargo is a package manager.
* `Cargo.toml` is used to manage dependencies.
* `Cargo new`, `Cargo init`, `Cargo build`, `Cargo run` are useful to know.
### Rust Syntax
Let's start getting familiar with basic Rust syntax.
#### Functions
We define functions in Rust using the keyword `fn` followed by function name, parameters in brackets which are followed by the arrow operator `->` return type.
```Rust=
fn function_name(function_parameters) ->return_type{definition;}
```
When you run ``` Cargo new``` it generates the main function of your program. Which looks like this and takes no parameters and does not return.
```Rust=
fn main() {
println!("Hello World!");
}
```
#### Variables And Data Types
Now let's move on to how Rust works with variable. To define a variable in Rust you can use keyword `let`. There are still multiple things to look at when you define a variable you can just use let and give it a value and compiler can infer the data type that it needs to use.
The list of data types that Rust supports can be seen [here](https://doc.rust-lang.org/book/ch03-02-data-types.html). For this example we will only be using some basic data types like `Integer`, `String`, `Float` and `Boolean`.
Now "let's" declare some variables. We will declare a variable and let the compiler infer its type and then we will explicitly define the type.
```Rust=
// Letting the compiler infer the data type
let variable_inferred = 32;
// Explicitly defining data type.
let variable_statically_typed: u64 = 32;
```
So what can happen when the compiler infers the type? The compiler can deduce the data type to be `i32` which is a signed integer. What about our intention was to define a variable that is an unsigned integer of 64 bits, for that we will have to tell the compiler to use `u64` . So depending on what we want to use we have to take care when to let compiler infer the data type and when to explicitly define it.
###### Mutability
By default the variables that we define are immutable but we can make it mutable using the keyword `mut` . You might be thinking about immutable state of variable and why it's the default. It's because Rust prefers safety by making sure variables are immutable by making the defualt variable binding immutable Rust makes sure that at compile time we know we are changing an immutable variable.
```Rust=
let mut mutable_variable = 32;
```
###### Constants
But we also have a keyword const that defines constants.
```Rust=
const PI: f32 = 3.14;
```
So what's the difference between const and immutable variable? first of all const doesn't allow mut keywords it's always immutable. Constants can be a part of global scope. They are also useful in defining constant expressions like we defined PI above. And we can keep it global so life of `PI` is useful for entire program. More on const [here](https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#differences-between-variables-and-constants)
###### Scope and Shadowing.
If you have programmed in other languages you must be familiar with the concept of scope. A variable's scope is where it can be accessed by the user and other parts of the program.
```Rust=
// Scope of a Variable
{
// This variable can only be accessed within the scope of these brackets.
let scoped_variable = "scope";
}
//If you try accessing scoped_variable here you will get an error. So be careful
```
Shadowing is a concept of using the let keyword to redefine the variable, it is important to note that we are redefining the variable and not using mut keyword to perform operations on it. These are two separate things. For Example:
```Rust=
// First definition
let shadow_variable = "xyz";
// Redefining the variable
let shadow_variable = "abc";
```
###### Example
Now let's write our own example using the concepts we have learned so far. Let's write a piece of code that adds a transaction to a hashmap everytime a new transaction is successful. It makes sure that two transaction IDs are not the same and we can also query if the transaction exists.
We can use database to manage transactions, but to keep it simple and for learning about Rust, we will be using `hashmap` provided by Rust's standard library `std::collections`.
```Rust=
use std::collections::HashMap;
pub fn insert_transaction(transaction_map: &mut HashMap<String, u64>, id_value: (String, u64)) -> bool {
if transaction_map.contains_key(&id_value.0)
{
return false;
}
else{
transaction_map.insert(id_value.0, id_value.1);
return true;
}
}
// TO check if transaction exists
pub fn transaction_exists(transaction_map: &mut HashMap<String, u64>, transactionid: String) -> bool {
if transaction_map.contains_key(&transactionid)
{
return true;
}
return false;
}
```
We can build up on this by adding other functions like `transaction_validite` that validates the transcation but for now it's okay.
#### Rust Tests
Now let's move towards writing tests. In Rust for writing tests we use the attribute `#[cfg(test)]` and they are mostly part of the module called tests which is defined by `mod tests`.
The individual functions are marked with `#[test]` so compiler knows these are tests. You can check for panic and tests and provide some other attributes but for simpilicity we won't be using them right now.
```Rust=
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction() {
// Defining a transaction map
let mut transaction_map: HashMap<String, u64> = HashMap::new();
// Inserting key for the first time
let transaction:(String, u64) = (String::from("ABC"), 30000);
assert_eq!(insert_transaction(&mut transaction_map, transaction), true);
// Inserting Same key and asserting we get a false
let transaction2:(String, u64) = (String::from("ABC"), 30000);
assert_eq!(insert_transaction(&mut transaction_map, transaction2), false);
}
}
```
### NEAR Setup
So before getting started it's important that we have `near-cli` setup on our machines. Make sure you have npm and node installed, and Node version is 12 or above. You can install by using.
```console
npm install -g NEAR-cli
```
We will be using the `near-cli` and will go through the commands as we need them for our project.
###### Introduction To NEAR SDK
Now let's take a look at [NEAR-SDK](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/) for Rust. NEAR SDK provides an interface on top of Rust to interact with the NEAR blockchain and develop smart contracts for NEAR platform.
We will be looking at some of the important parts of the SDK that will be needed for developing basic smart contract and NFTs.
Lets start with a list of [attributes](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/#attributes "NEAR Attributes").
```Rust=
#[NEAR_bindgen]
Here is a helpful video.
{%youtube eVmVMrwZW0g %}
```
##### A Basic Contract Using NEAR
Let's discuss a basic smart contract that has been written in Rust. For this purpose we will be using the [basic counter](https://github.com/NEAR-examples/Rust-counter "Rust Counter") example.
###### Cargo
So we build our contracts on NEAR platform as a [library](https://hackmd.io/@NEARly-learning/contract-basics-Rust) . Remember the `Cargo new` command we used to build it as a library we will have to use `--lib`, this specifies that the new package should be built as a library.
```console
Cargo new Rust_counter --lib
```
Lets talk about dependencies for NEAR-SDK. If you are coming from C++ adding dependencies can be a tedious process but Rust provides an easy interface using Cargo. Just open your Cargo.toml file and add this under dependencies.
```toml
[dependencies]
NEAR-SDK = "3.1.0"
[dev-dependencies]
NEAR-SDK-sim = "3.1.0"
```
###### **Code**
Wrap a Rust struct in `#[NEAR_bindgen]` and it generates a smart contract compatible with the NEAR blockchain. That’s it. Every Rust contract struct must be tagged with this macro since it exposes the execution environment to the contract allowing it to receive method calls, send results and generally behave like a contract within the runtime environment. More on this [here](https://hackmd.io/@NEARly-learning/contract-basics-Rust#Lines-13---15).
For our example we will be using a struct called `Counter` with one `i8` variable. Borsh macros are used for serailizing and deserializing on the NEAR blockchain platform.
```Rust=
#[NEAR_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct Counter {
// See more data types at https://doc.Rust-lang.org/book/ch03-02-data-types.html
val: i8, // i8 is signed. unsigned integers are also available: u8, u16, u32, u64, u128
}
```
Now let's discuss functions that will be implemented for our counter. These will be part of `impl Counter` as we want to implement them for our type `Counter`.
`get_num`
Return the current counter number. This must match the data type of the 8 bit signed integer we provided above
```Rust=
pub fn get_num(&self) -> i8 {
return self.val;
}
```
`increment`
Increment the `counter` by one. Look how we provide `&mut self` as a parameter that's because we want to change the value of our variable. `after_counter_change` is called which we will discuss later.
```Rust=
pub fn increment(&mut self) {
// note: adding one like this is an easy way to accidentally overflow
// real smart contracts will want to have safety checks
// e.g. self.val = i8::wrapping_add(self.val, 1);
// https://doc.Rust-lang.org/std/primitive.i8.html#method.wrapping_add
self.val += 1;
let log_message = format!("Increased number to {}", self.val);
env::log(log_message.as_bytes());
after_counter_change();
}
```
`decrement`
Again like increment this changes the value so we need `&mut` and it decreases the value by one. Also `after_counter_change` is called which we will discuss later.
```Rust=
pub fn decrement(&mut self) {
// note: subtracting one like this is an easy way to accidentally overflow
// real smart contracts will want to have safety checks
// e.g. self.val = i8::wrapping_sub(self.val, 1);
// https://doc.Rust-lang.org/std/primitive.i8.html#method.wrapping_sub
self.val -= 1;
let log_message = format!("Decreased number to {}", self.val);
env::log(log_message.as_bytes());
after_counter_change();
}
```
`reset`
Reset the counter value to zero. Depends on our need.
```Rust=
pub fn reset(&mut self) {
self.val = 0;
// Another way to log is to cast a string into bytes, hence "b" below:
env::log(b"Reset counter to zero");
}
```
Now the rest of the code involves a function that checks if the value has gone above 127 or below 128 as we don't want the value to overflow our 8bit signed integer.
```Rust=
fn after_counter_change() {
// show helpful warning that i8 (8-bit signed integer) will overflow above 127 or below -128
env::log("Make sure you don't overflow, my friend.".as_bytes());
}
```
You can take a look at full example and unit tests for this implementation [here](https://github.com/NEAR-examples/Rust-counter/blob/master/contract/src/lib.rs#L128)
If you want to learn more about NEAR and it's basics. Here is a good [playlist](https://www.youtube.com/playlist?list=PL9tzQn_TEuFWJwvBg5V6EVa2DtYL_-2lJ) to get you started.
###### NFTs
Before moving on to talking about implementing NFTs on NEAR, lets briefly talk about what NFTs are.
NFT stands for **Non-Fungible Token**. To fully understand what Non-Fungible Tokens are we will have to understand the concept of **Fungible things** and **Fungible Tokens**.
Fungible tokens or things can be exchanged (or for ease of understanding traded with each other directly) because they are not associated to the uniqueness of the items. Their worth is not dependent on anyone who owns them, there is a set of rules that is followed for their trading, For example the market decides how many NEAR protocol coins can be traded for a Bitcoin and not some one entity.
On the other hand non-fungible things can be thought of as valuable possession like a painting or a piece of art, and each non-fungible thing or token has unique property assigned to it that only that NFT holds and no body else can have that.
Lets just say for ease of understanding NFTs are another way that we can leverage blockchain technology apart from cryptocurrencies and mostly to have digital assets.
##### NFTs on NEAR:
Let's explore writing an NFT on NEAR using Rust-NEAR-SDK. [Protocol specifications](https://nomicon.io/) and [documentation](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/) will be handy. Bookmark them and keep them as your friend for further development.
First of all lets talk about what you have to build. And how you have to structure the project let's use this [example](https://github.com/NEAR-examples/NFT) to discuss.
We will start off with `Cargo.toml` file. We will specify that we will build it as a library which has been discussed above, and then we will add the dependencies for our examples that are [**NEAR-SDK**](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/) and [**NEAR-contract-standards**](https://docs.rs/NEAR-contract-standards/3.2.0/NEAR_contract_standards/)
```toml
[package]
name = "non-fungible-token"
version = "1.1.0"
authors = ["NEAR Inc <hello@NEAR.org>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
NEAR-SDK = "3.1.0"
NEAR-contract-standards = "3.1.1"
```
Next we will talk about the code and what we will need to write code for an NFT on NEAR. In our example we will be implementing an NFT with JSON serialization and taking into account some of the important details discussed in the GitHub [Example](https://github.com/near-examples/NFT/blob/master/nft/src/lib.rs#L3).
```Rust=
/*!
NOTES:
- The maximum balance value is limited by U128 (2**128 - 1).
- JSON calls should pass U128 as a base-10 string. E.g. "100".
- The contract optimizes the inner trie structure by hashing account IDs. It will prevent some
abuse of deep tries. Shouldn't be an issue, once NEAR clients implement full hashing of keys.
- The contract tracks the change in storage before and after the call. If the storage increases,
the contract requires the caller of the contract to attach enough deposit to the function call
to cover the storage cost.
This is done to prevent a denial of service attack on the contract by taking all available storage.
If the storage decreases, the contract will issue a refund for the cost of the released storage.
The unused tokens from the attached deposit are also refunded, so it's safe to
attach more deposit than required.
- To prevent the deployed contract from being modified or deleted, it should not have any access
keys on its account.
*/
```
Now lets talk about what part of NEAR SDK and `NEAR-contract SDK` we will be using in this NFT.
First up are the [contract metadata](https://docs.rs/NEAR-contract-standards/3.2.0/NEAR_contract_standards/non_fungible_token/metadata/index.html) as per the specifications for NFT protocol imported from NEAR-contract-SDK. and then there are NFT [token and token struct](https://docs.rs/NEAR-contract-standards/3.2.0/NEAR_contract_standards/non_fungible_token/struct.Token.html) as per specs. After that we will use the [non_fungible_token](https://docs.rs/NEAR-contract-standards/3.2.0/NEAR_contract_standards/non_fungible_token/index.html) type trait for our NFT. Using SDK for `near-contract-standards` makes our programming job a lot easier as we don't have to worry about implementing specifications on our own and can build on top of it.
```Rust=
use NEAR_contract_standards::non_fungible_token::metadata::{
NFTContractMetadata, NonFungibleTokenMetadataProvider, TokenMetadata, NFT_METADATA_SPEC,
};
use NEAR_contract_standards::non_fungible_token::{Token, TokenId};
use NEAR_contract_standards::non_fungible_token::NonFungibleToken;
```
Then we will import things we need from NEAR SDK for serialization and [container](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/collections/index.html) for storing our contract details apart from how to add helper for json [json types](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/json_types/index.html), type traits for [Borsh Storage Key](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/derive.BorshStorageKey.html), [Panic On Default](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/derive.PanicOnDefault.html),
[Promise](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/struct.Promise.html) A structure representing a result of the scheduled execution on another contract. or [PromiseOrValue](https://docs.rs/NEAR-SDK/3.1.0/NEAR_SDK/enum.PromiseOrValue.html).
```Rust=
use NEAR_SDK::borsh::{self, BorshDeserialize, BorshSerialize};
```
```Rust=
use NEAR_SDK::collections::LazyOption;
```
```Rust=
use NEAR_SDK::json_types::ValidAccountId;
```
```Rust=
use NEAR_SDK::{
env, NEAR_bindgen, AccountId, BorshStorageKey, PanicOnDefault, Promise, PromiseOrValue,
};
```
To start if off we will use `setup_alloc!()` macro boilerplate for setting up allocator used in WASM binary. And a contract struct, for storing our contract's token and metadata.
```Rust=
NEAR_SDK::setup_alloc!();
#[NEAR_bindgen]
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
pub struct Contract {
tokens: NonFungibleToken,
metadata: LazyOption<NFTContractMetadata>,
}
```
Here we are defining an example to be used as our NFT and an enum that we will serialize to be used on NEAR Platform.
```Rust=
const DATA_IMAGE_SVG_NEAR_ICON: &str = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 288 288'%3E%3Cg id='l' data-name='l'%3E%3Cpath d='M187.58,79.81l-30.1,44.69a3.2,3.2,0,0,0,4.75,4.2L191.86,103a1.2,1.2,0,0,1,2,.91v80.46a1.2,1.2,0,0,1-2.12.77L102.18,77.93A15.35,15.35,0,0,0,90.47,72.5H87.34A15.34,15.34,0,0,0,72,87.84V201.16A15.34,15.34,0,0,0,87.34,216.5h0a15.35,15.35,0,0,0,13.08-7.31l30.1-44.69a3.2,3.2,0,0,0-4.75-4.2L96.14,186a1.2,1.2,0,0,1-2-.91V104.61a1.2,1.2,0,0,1,2.12-.77l89.55,107.23a15.35,15.35,0,0,0,11.71,5.43h3.13A15.34,15.34,0,0,0,216,201.16V87.84A15.34,15.34,0,0,0,200.66,72.5h0A15.35,15.35,0,0,0,187.58,79.81Z'/%3E%3C/g%3E%3C/svg%3E";
#[derive(BorshSerialize, BorshStorageKey)]
enum StorageKey {
NonFungibleToken,
Metadata,
TokenMetadata,
Enumeration,
Approval,
}
```
Next we will implement details for our contract type trait that includes defining the newmetadata for our NFT details for its owner and the method to mint in on NEAR platform.
```Rust=
#[NEAR_bindgen]
impl Contract {
/// Initializes the contract owned by `owner_id` with
/// default metadata (for example purposes only).
#[init]
pub fn new_default_meta(owner_id: ValidAccountId) -> Self {
Self::new(
owner_id,
NFTContractMetadata {
spec: NFT_METADATA_SPEC.to_string(),
name: "Example NEAR non-fungible token".to_string(),
symbol: "EXAMPLE".to_string(),
icon: Some(DATA_IMAGE_SVG_NEAR_ICON.to_string()),
base_uri: None,
reference: None,
reference_hash: None,
},
)
}
#[init]
pub fn new(owner_id: ValidAccountId, metadata: NFTContractMetadata) -> Self {
assert!(!env::state_exists(), "Already initialized");
metadata.assert_valid();
Self {
tokens: NonFungibleToken::new(
StorageKey::NonFungibleToken,
owner_id,
Some(StorageKey::TokenMetadata),
Some(StorageKey::Enumeration),
Some(StorageKey::Approval),
),
metadata: LazyOption::new(StorageKey::Metadata, Some(&metadata)),
}
}
/// Mint a new token with ID=`token_id` belonging to `receiver_id`.
///
/// Since this example implements metadata, it also requires per-token metadata to be provided
/// in this call. `self.tokens.mint` will also require it to be Some, since
/// `StorageKey::TokenMetadata` was provided at initialization.
///
/// `self.tokens.mint` will enforce `predecessor_account_id` to equal the `owner_id` given in
/// initialization call to `new`.
#[payable]
pub fn NFT_mint(
&mut self,
token_id: TokenId,
receiver_id: ValidAccountId,
token_metadata: TokenMetadata,
) -> Token {
self.tokens.mint(token_id, receiver_id, Some(token_metadata))
}
}
```
Here we are using the [macros](https://docs.rs/NEAR-contract-standards/3.2.0/NEAR_contract_standards/index.html#macros) provided by `near_contract_standards` depending on our requirement.
`impl_non_fungible_token_approval` Non-fungible token approval management allows for an escrow system where multiple approvals for each token exist.
`impl_non_fungible_token_core` The core methods for a basic non-fungible token. Extension standards may be added in addition to this macro.
`impl_non_fungible_token_enumeration` Non-fungible enumeration adds the extension standard offering several view-only methods to get token supply, tokens for each owner, etc.
```Rust=
NEAR_contract_standards::impl_non_fungible_token_core!(Contract, tokens);
NEAR_contract_standards::impl_non_fungible_token_approval!(Contract, tokens);
NEAR_contract_standards::impl_non_fungible_token_enumeration!(Contract, tokens);
```
This function has been implemented to get the NFT metadata from our contract.
```Rust=
#[NEAR_bindgen]
impl NonFungibleTokenMetadataProvider for Contract {
fn NFT_metadata(&self) -> NFTContractMetadata {
self.metadata.get().unwrap()
}
}
```
You can look for [tests](https://github.com/NEAR-examples/NFT/blob/master/NFT/src/lib.rs#L116) and implementations and usage of the contract on GitHub [NFT example](https://github.com/NEAR-examples/NFT). The example also has test implementations.
#### Conclusion
Rust provides a way to write fast and safe code, and it can be leveraged for use cases where that's a need. Rust community also provides great introductory documents and is quite welcoming for beginners. NEAR SDK provides a great way to write contracts for NEAR protocol using Rust. In this post we have discussed some basics of Rust and how to use NEAR SDK for writing NFT. There are a lot of things that are to be learned when it comes to Rust and it might seem difficult but if we can break it down for our understanding it can get easier.