stacking pool factory: This contract deploys and automatically whitelists new staking pool contracts. It allows any user to create a new whitelisted staking pool.The staking pool factory contract packages the binary of the staking pool contract within its own binary. To create a new stakin
g pool a user should issue a function call transaction and attach the required minimum deposit. The entire deposit will be transferred to the newly created staking pool contract in order to cover the required storage.
Rust has variables like any other language such as primatives and non-primatives and there are some special features we will disscus them one by one.
pub denominator: u32,
here we are defining a variable as a parameter of type 'u32'
const MIN_ATTACHED_BALANCE: Balance = 30_000_000_000_000_000_000_000_000;
Rust has compile time const that recommended to be used with values that used many times in addition to it gives readablity for the code, for example here we define a const as 'Balance'.
pub struct StakingPoolFactory {
/// Account ID of the staking pool whitelist contract.
staking_pool_whitelist_account_id: AccountId,
/// The account ID of the staking pools created.
staking_pool_account_ids: UnorderedSet<AccountId>,
Rust doesn't have classes but instead struct to define a container for data like in these line of code we define a struct with it's variable
testing_env_with_promise_results(context.clone(), PromiseResult::Successful(vec![]));
vector is a dynamic list of homogeneous data stored in the heap, so here we are creating vector with no element with the help of macro vec![]
rust has many types that represents sequence of characters
staking_pool_id: String,
String is a UTF-8 dynamic groupable list used to store sequence of chars, and we here we defining a type of String
b"new".to_vec(),
it is slice version of String so it reference to other type of String ,here we are haveing a string literal of type '&'static str' and this mean it reference to a string with static lifetime which will live all program lifetime and call to_string function on it to convert it to owned string
like any other language you can define some reusable code and it is called function.
pub fn create_staking_pool(
here we are defining a function with name 'create_staking_pool',with parameters and return type
Rust is not object oriented language but you can define structs as data container and attach function to operate on this data, also you can do the same for enums.
impl RewardFeeFraction {
pub fn assert_valid(&self) {
assert_ne!(self.denominator, 0, "Denominator must be a positive number");
assert!(
self.numerator <= self.denominator,
"The reward fee must be less or equal to 1"
);
}
}
here we start to define functions to manipulate the derived type
pub fn new(staking_pool_whitelist_account_id: AccountId) -> Self {
inside impl block we can define a static function like this so it can be called from the type itself, here we are returning Self from the function so here we refere to the current type.
static function call
staking_pool_account_ids: UnorderedSet::new(b"s".to_vec()),
here we call a static function 'new' in the type 'UnorderedSet' and this is widely used method to create a new instance because Rust doesn't have constractor.
pub fn create_staking_pool(
&mut self,
staking_pool_id: String,
owner_id: AccountId,
stake_public_key: Base58PublicKey,
reward_fee_fraction: RewardFeeFraction,
) -> Promise {
inside impl block we can define instance function like this code defines function that takes parameters in addition to '& mut self', rust introduce new concept called mutability so here we borrow mutable refrence for the current object so that the function can edit on it.
pub fn get_min_attached_balance(&self) -> U128 {
MIN_ATTACHED_BALANCE.into()
}
here we define an instance function with '&self' this means the function borrows the current object for readonly like in first line we use it to get data from the current object
let mut context = VMContextBuilder::new()
.current_account_id(account_factory())
.predecessor_account_id(account_near())
here we are creating a variable and call chain of instance functions on it and this is because here we are returning same object from the function
.expect("No such request")
this function implemented for Option and Result type in rust in order to unwrap the inner value
rust has unique concept called ownership and borrowing so when you assign variables to anther so most of the time instead of copying, the ownership taken by the second variable, so we can use borrowing and other things so keep the origin variable
Clone and ownership
Promise::new(staking_pool_account_id.clone())
as we mention when we use a variable in passing to function or to assign anther variable so the ownership is transfered and the origin variable move and this exist with non-primative types unless they implement Copy trait, so here we use clone function to make a copy of data and so as not to be moved and reused after that
Every programming language has tools for effectively handling the duplication of concepts and write extensible agnostic
pub trait ExtWhitelist {
fn add_staking_pool(&mut self, staking_pool_account_id: AccountId) -> bool;
}
trait define shared behaviour between implementors and it is consider as a contract, here we define a trait 'ExtSelf ' with it's abstracted functionality
trait impl
impl Default for StakingPoolFactory {
fn default() -> Self {
env::panic(b"The contract should be initialized before usage")
}
}
here we impl Default trait for 'StakingPoolFactory ' type
Tests are Rust functions that verify that the non-test code is functioning in the expected manner.
#[test]
fn test_create_staking_pool_success() {
so here we are writing a function and anntotate it as test like at first line
Rust structure it's code in modules so you can have mutliple files in your projects.
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::UnorderedSet;
use near_sdk::json_types::{Base58PublicKey, U128};
use near_sdk::serde::{Deserialize, Serialize};
these lines import struct types from other crates that must be mentioned as a dependence in Cargo.toml.
pub mod gas {
we can define new module in rust to structure our code, here we declaring a module
Macros are widely used in metaprogramming for generating code at compile time.
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize)]
pub struct StakingPoolFactory {
procedure macros allow creating syntax extentions like at first line we use attribute for the struct to generate some code in order to validate near contract, at second line derive-macro to generate implementation for these traits instead of making custom implementation for them.BorshDeserialize, BorshSerialize are used to convert to and from object and binary value but Serialize and Deserialize are used to convert to and from object and json value.
#[payable]
payable allows the method to accept token transfer together with the function call
#[derive(Serialize, Deserialize, Clone)]
#[serde(crate = "near_sdk::serde")]
used to select serde crate and so we give it a path to it here
#[should_panic(expected = "Not enough attached deposit to complete staking pool creation")]
fn test_create_staking_pool_not_enough_deposit() {
we attribute a test function with this macro to say that it might panic with this message
#[global_allocator]
In a given program, the standard library has one 'global' memory allocator that is used for example by Box
#[ext_contract(ext_self)]
helper macro that allows you to make cross-contract calls with the syntax
assert!(
self.numerator <= self.denominator,
"The reward fee must be less or equal to 1"
);
macro rules it is a macro like function but evaluated at compile time in addition to have ability to generate codes, this line call macro and as we see it is like function call but have '!' so here we call macro called assert to assert some thing is true
.deploy_contract(include_bytes!("../../staking-pool/res/staking_pool.wasm").to_vec())
here we are calling a macro called include_bytes to get data from specific path