Near Protocol and Solana

NEAR Protocol and Solana

To Share and +4 nLEARNs

Today let’s look at two of the most promising smart contract platforms in the world – Near Protocol and Solana. While Ethereum still dominates this market, the fact is that its lack of scalability and high fees have forced most developers to look for viable alternatives. Near and Solana have emerged as the top two front runners.

What is Solana?

Solana was founded in 2017 by Anatoly Yakovenko, who had previously worked at DropBox. Yakovenko, along with Eric Williams and CTO Greg Fritzgerald, created Solana to solve the existing problems in Bitcoin and Ethereum. The project has attracted investments from Multicoin Capital, Foundation Capital, SLOW Capital, CMCC Global, Abstract Ventures, and more. 

NEAR Solana

Features of the Solana blockchain

  • 50,000 transactions per second and 0.4 seconds block time
  • The system can provide 28.4 million transactions per second on a 40 gigabit network.
  • Solana uses Proof-of-History consensus algorithm.

How does Proof-of-History (PoH) work?

In a decentralized network that spreads over a large area, consensus is essential. Bitcoin uses the proof-of-work (PoW) consensus to take care of consensus. While the method is highly secure, it is hard not to ignore its most significant problem – the lack of scalability. Don’t forget that Bitcoin can only do 7 transactions per second.

Solana uses proof-of-history, wherein it creates historical records to prove that an event occurs during a specific moment in time. Here are some points that you need to keep in mind:

  • The algorithm uses a high-frequency Verifiable Delay Function which requires a certain number of sequential steps to finish.
  • The transactions or events clocked within the network will be designated a unique hash a count, which can be publicly verified.
  • The count allows the network to know exactly when the transaction or the event has happened.
  • Every node has a cryptographic clock that helps track the network time and order of events.

Due to PoH, the Solana network supports 50,000 transactions per second while running with GPUs. 

What is a Solana Cluster?

A cluster is a set of independently-owned computers working together and can be viewed as a singular system. The main features of the Cluster are as follows:

  • They help verify the output of untrusted, user-submitted programs. 
  • Maintains a record of any transaction or event that a user makes.
  • It keeps track of which computers did meaningful work to keep the network running.
  • It keeps track of the possession of real-world assets.

Which of the following utilizes sharding?

Correct! Wrong!

Programming in Solana

The smart contracts in Solana are written in Rust or C and compiled to Berkeley Packet Filter (BPF) bytecode. Since there are more toolings available, it is recommended that you code in Rust. Beginners should code their programs using the Anchor framework, which simplifies execution.

Solana has a unique account model that is similar to files in the Linux operating system. They can hold any arbitrary data and also contain metadata about how they can be accessed. Keep in mind though, that the accounts have a fixed size and cannot be resized. 

Solana’s current programming model may force you to move application logic off-chain or modify the functionality to be inefficient and limited by the fixed account size. 

Which of the following is an integral part of Solana?

Correct! Wrong!

Example Contract

#![feature(proc_macro_hygiene)]

use anchor_lang::prelude::*;
use anchor_spl::token::{self, TokenAccount, Transfer};

#[program]
pub mod plutocratic_hosting {
    use super::*;

    /// Initialize a new contract with initialized content.
    #[access_control(Initialize::validate(&ctx, nonce))]
    pub fn initialize(
        ctx: Context<Initialize>,
        price: u64,
        content: String,
        nonce: u8,
    ) -> ProgramResult {

        // Transfer funds to the contract vault.
        let cpi_accounts = Transfer {
            from: ctx.accounts.from.to_account_info().clone(),
            to: ctx.accounts.vault.to_account_info().clone(),
            authority: ctx.accounts.owner.clone(),
        };
        let cpi_program = ctx.accounts.token_program.clone();
        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
        token::transfer(cpi_ctx, price)?;

        // Initialize the content data.
        let content_record = &mut ctx.accounts.content;
        content_record.price = price;
        content_record.content = content;
        content_record.nonce = nonce;
        content_record.owner = *ctx.accounts.owner.to_account_info().key;
        content_record.vault = *ctx.accounts.vault.to_account_info().key;
        Ok(())

    }

    /// Purchase content address for new price, if transferring more tokens.
    #[access_control(check_funds(&ctx.accounts.content, price))]
    pub fn purchase(ctx: Context<Purchase>, price: u64, content: String) -> ProgramResult {
        // Transfer funds from contract back to owner.
        let seeds = &[
            ctx.accounts.content.to_account_info().key.as_ref(),
            &[ctx.accounts.content.nonce],
        ];
        let signer = &[&seeds[..]];
        let cpi_accounts = Transfer {
            from: ctx.accounts.vault.to_account_info().clone(),
            to: ctx.accounts.owner_token.to_account_info().clone(),
            authority: ctx.accounts.contract_signer.clone(),
        };
        let cpi_program = ctx.accounts.token_program.clone();
        let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
        token::transfer(cpi_ctx, ctx.accounts.content.price)?;

        // Transfer funds from new owner to contract.
        let cpi_accounts = Transfer {
            from: ctx.accounts.new_owner_token.to_account_info().clone(),
            to: ctx.accounts.vault.to_account_info().clone(),
            authority: ctx.accounts.new_owner.clone(),
        };
        let cpi_program = ctx.accounts.token_program.clone();
        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
        token::transfer(cpi_ctx, price)?;

        // Overwrite content
        let content_record = &mut ctx.accounts.content;
        content_record.price = price;
        content_record.content = content;
        content_record.owner = *ctx.accounts.new_owner.to_account_info().key;

        Ok(())
    }
}

#[account]
pub struct ContentRecord {
    /// Price at which the current content is owned.
    pub price: u64,
    /// Content Data.
    pub content: String,
    /// Public key of current owner of the content.
    pub owner: Pubkey,
    /// Address for token program of funds locked in contract.
    pub vault: Pubkey,
    /// Nonce for the content, to create valid program derived addresses.
    pub nonce: u8,
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init)]
    content: ProgramAccount<'info, ContentRecord>,
    #[account(mut, "&vault.owner == contract_signer.key")]
    vault: CpiAccount<'info, TokenAccount>,
    /// Program derived address for the contract.
    contract_signer: AccountInfo<'info>,
    /// Token account the contract is made from.
    #[account(mut, has_one = owner)]
    from: CpiAccount<'info, TokenAccount>,
    /// Owner of the `from` token account.
    owner: AccountInfo<'info>,
    token_program: AccountInfo<'info>,
    rent: Sysvar<'info, Rent>,
}


impl<'info> Initialize<'info> {
    pub fn validate(ctx: &Context<Self>, nonce: u8) -> Result<()> {
        let signer = Pubkey::create_program_address(
            &[
                ctx.accounts.content.to_account_info().key.as_ref(),
                &[nonce],
            ],
            ctx.program_id,
        )
        .map_err(|_| ErrorCode::InvalidNonce)?;
        if &signer != ctx.accounts.contract_signer.to_account_info().key {
            return Err(ErrorCode::InvalidSigner.into());
        }
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Purchase<'info> {
    #[account(mut, has_one = vault)]
    content: ProgramAccount<'info, ContentRecord>,
    #[account(mut)]
    vault: CpiAccount<'info, TokenAccount>,
    #[account(seeds = [
        content.to_account_info().key.as_ref(),
        &[content.nonce],
    ])]
    contract_signer: AccountInfo<'info>,
    #[account(mut, has_one = owner)]
    owner_token: CpiAccount<'info, TokenAccount>,
    #[account(mut)]
    new_owner_token: CpiAccount<'info, TokenAccount>,
    #[account(signer)]
    new_owner: AccountInfo<'info>,
    owner: AccountInfo<'info>,
    token_program: AccountInfo<'info>,
}

fn check_funds(check: &ContentRecord, new_price: u64) -> Result<()> {
    if check.price >= new_price {
        return Err(ErrorCode::InsufficientFunds.into());
    }

    Ok(())
}

#[error]
pub enum ErrorCode {
    #[msg("The given nonce does not create a valid program derived address.")]
    InvalidNonce,
    #[msg("The derived signer does not match that which was given.")]
    InvalidSigner,
    #[msg("Insufficient funds provided to purchase route.")]
    InsufficientFunds,
}

What’s happening in the contract?

  • All accounts to be accessed are annotated in structure for each call with #[derive(Accounts)]. 
  • The functions help intialize the account data for the initial owner and Purchase. This allows anyone to purchase it for more tokens. 
  • Temporary data is passed into function parameters. These parameters are inside the initialize and purchase functions. This also include the Context holding the accounts requires for the transaction.
  • The contract’s state is located in the ContentRecord struct. This is further annotated with #[account] to indicate that it represents the data layout for an account.

What is NEAR Protocol?

Coming into existence in the summer of 2018, the protocol was designed to create the perfect environment for decentralized applications by providing higher speeds, higher throughput, and better compatibility with other chains. NEAR has a unique Sharding technique and introduces a block generation mechanism called “Doomslug” proposed in 2019. Doomslug allows practical or “Doomslug” finality, ensuring that the blocks receive finality in seconds.

 

NEAR protocol is developed by the NEAR Collective, a community of developers and researchers collaborating on building the project. Some critical features of NEAR are:

  • NEAR is a sharded system that allows for infinite scalability. 
  • An easy-to-use protocol, NEAR allows developers to build apps easily and quickly. 
  • NEAR is not a side chain but a Layer-1 protocol. 
  • dApps created using NEAR run on top of the underlying NEAR layer. 

What Is The NEAR Collective? 

The NEAR Collective includes individual organizations and other contributors that are continuously working on improving the NEAR Protocol. The Collective works on projects such as writing the initial code and implementation for the NEAR Network. NEAR is entirely decentralized, operating independently, and cannot be shut down or manipulated, even by those that built it.

It is a non-profit organization that is focused on creating a vibrant ecosystem around the NEAR blockchain. It helps in the coordination of governance activities and development. The NEAR Collective has several projects, with the NEAR blockchain just one of several projects under the aegis of the collective. 

NEAR Consensus Mechanism

The consensus mechanism implemented on NEAR is called Nightshade. Nightshade models the system as a single blockchain. The list of all the transactions in each block is split into physical chunks, one chunk per shard. All chunks accumulate to one block. Note that chunks can only be validated by nodes that maintain the state of that shard.

Speaking of validation, a key component of NEAR are the validators. These validators are responsible for maintaining consensus within the protocol. Validators are specialized nodes that need to keep their servers online 100% of the time while keeping their systems continually updated. 

Here are a few points that you must keep in mind about network validators.

  • NEAR determines its network validators every new epoch, electing them based on their stake.
  • The already elected validators are re-enrolled by automatically re-staking their tokens plus the accrued rewards.
  • Potential validators have to have their stake above a dynamically determined level.
  • There are two methods that a validator can use to strengthen their stake – buy the tokens themself or borrow via stake delegation.
  • The reward you receive is directly proportional to your stake—more your stake, more your rewards.

The consensus is based on the heaviest chain consensus. Meaning, once a block producer publishes a block, they collect the signatures of validator nodes. The weight of a block is then the cumulative stake of all the signers whose signatures are included in the block. The weight of a chain is the sum of the block weights. Additionally, the consensus utilizes a finality gadget that introduces additional slashing conditions for higher chain security.

Doomslug” is a block generation mechanism of which protocol?

Correct! Wrong!

Aurora and NEAR Protocol

Aurora has also launched on the NEAR Protocol, providing an Ethereum Layer-2 experience. Some of the ways that Aurora improves NEAR are: 

  • It helps boost throughput to thousands of transactions per second.
  • A block finality time of 2 seconds.
  • Futureproof ecosystem growth
  • Low transaction fees, which are 1000x lower than Ethereum.
  • Uncompromising compatibility with Ethereum.

Example Contract

use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::collections::LookupMap;
use near_sdk::{env, near_bindgen, AccountId, Balance, Promise};

#[global_allocator]
static ALLOC: near_sdk::wee_alloc::WeeAlloc = near_sdk::wee_alloc::WeeAlloc::INIT;

#[derive(BorshDeserialize, BorshSerialize)]
pub struct ContentRecord {
    pub price: Balance,
    pub content: String,
    pub owner: AccountId,
}

#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize)]
pub struct ContentTracker {
    values: LookupMap<String, ContentRecord>,
    contract_owner: AccountId,
}

impl Default for ContentTracker {
    fn default() -> Self {
        let contract_owner = env::predecessor_account_id();
        Self {
            values: LookupMap::new(b"v".to_vec()),
            contract_owner,
        }
    }
}

#[near_bindgen]
impl ContentTracker {
    /// Gets content at a given route.
    pub fn get_route(&self, route: String) -> Option<String> {
        self.values.get(&route).map(|v| v.content)
    }

    /// Purchases a route based on funds sent to the contract.
    #[payable]
    pub fn purchase(&mut self, route: String, content: String) {
        let deposit = env::attached_deposit();
        assert!(deposit > 0);
        if let Some(entry) = self.values.get(&route) {
            assert!(
                deposit > entry.price,
                "Not enough deposit to purchase route, price: {} deposit: {}",
                entry.price,
                deposit
            );


            // Refund purchase to existing owner
            Promise::new(entry.owner).transfer(entry.price);
        }


        // Update record for the contract state.
        self.values.insert(
            &route,
            &ContentRecord {
                price: deposit,
                content,
                owner: env::predecessor_account_id(),
            },
        );
    }


    /// Allows owner of the contract withdraw funds.
    pub fn withdraw(&mut self) {
        assert_eq!(env::predecessor_account_id(), self.contract_owner);


        // Send the contract funds to the contract owner
        Promise::new(self.contract_owner.clone()).transfer(env::account_balance());
    }
}

What is going on in the contract?

So, what is going on here in the contract? Let’s take a closer look.

  • The contract is defined by #[near_bindgen] in the ContentTracker it is similar to a constructor in Solidity and gets called when contract is deployed.
  • The purchase function is annotated with #[payable].
  • The contract includes asynchronous calls in the form of  Promise::new(…).transfer(…); lines.
  • The data structure LookupMap<String, ContentRecord> handles the key-value lookup, which accesses storage. This is equal to Solidity “mapping.”

Conclusion

Both Solana and NEAR Protocol are brilliant smart contract platforms that have built up highly active communities. Both of them bring in special features that help combat the biggest issue plaguing the decentralized world – speed. Both of the platforms are still growing and have a lot of promise.

33
Scroll to Top