Intermediate

 

How to develop a smart contract on Near Blockchain – Part 2

This tutorial is a continuation of “How to develop a smart contract on Near Blockchain – Part 1.” In this part, we will continue exploring the NEAR blockchain and how to develop smart contracts on it. The tutorial provides information on the environment variables associated with a method execution on NEAR, which includes the users’ information and balances, time and gas limits, and related variables. Understanding these variables is essential for efficient smart contract development on the NEAR blockchain. The tutorial provides code snippets to help understand the concepts and concludes with tips and warnings to ensure that the best practices are being followed in smart contract development.

 

Environment

Every method execution has an environment associated with information such as:

  • Who called the method

  • How much money is attached to the call

  • How many computational resources are available

  • The current timestamp

  • Helper functions for Public Key derivation, for example

 

Environment Variables

  • AssemblyScript

 
 

Variable Name

 

SDK Variable

 

Description

 

Predecessor

context.predecessor

Account ID that called this method

Current Account

context.contractName

Account ID of this smart contract

Signer

context.sender

Account ID that signed the transaction leading to this execution

Attached Deposit

context.attachedDeposit

Amount in NEAR attached to the call by the predecessor

Account Balance

context.accountBalance

Balance of this smart contract (including Attached Deposit)

Prepaid Gas

context.prepaidGas

Amount of gas available for execution

Timestamp

context.blockTimestamp

Current timestamp (number of non-leap-nanoseconds since January 1, 1970 0:00:00 UTC)

Current Epoch

context.epochHeight

Current epoch in the blockchain

Block Index

context.blockIndex

Current block index (a.k.a. block height)

Storage Used

context.storageUsage

Current storage used by this smart contract

Used Gas

context.usedGas

Amount of gas used for execution

Signer Public Key

context.senderPublicKey

Sender Public Key

Account Locked Balance

context.accountLockedBalance

Balance of this smart contract that is locked

 

 

Who is Calling? Who am I?

The environment gives you access to 3 important users: the current_account, the predecessor, and the signer.

 

Current Account

The current_account contains the address in which your contract is deployed. This is very useful to implement ownership, e.g. making a public method only callable by the contract itself.

 

Predecessor and Signer

The predecessor is the account that called the method in the contract. Meanwhile, the signer is the account that signed the initial transaction.

During a simple transaction (no cross-contract calls) the predecessor is the same as the signer. For example, if alice.near calls contract.near, from the contract’s perspective, alice.near is both the signer and the predecessor. However, if contract.near creates a cross-contract call, then the predecessor changes down the line. In the example bellow, when pool.near executes, it would see contract.near as the predecessor and alice.near as the signer.

You can access information about the users interacting with your smart contract

 

TIP: In most scenarios you will only need to know the predecessor. However, there are situations in which the signer is very useful. For example, when adding NFTs into this marketplace, the contract checks that the signer, i.e. the person who generated the transaction chain, is the NFT owner.

 

Balances and Attached NEAR

The environment gives you access to 3 token-related parameters, all expressed in yoctoNEAR (1 Ⓝ = 1024yⓃ):

 

Attached Deposit

attached_deposit represents the amount of yoctoNEAR the predecessor attached to the call.

This amount is already deposited in your contract’s account, and is automatically returned to the predecessor if your method panics.

DANGER: If you make a cross-contract call and it panics, the funds are sent back to your contract. See how to handle this situation in the callback section

 

Account Balance

account_balance represents the balance of your contract (current_account).

It includes the attached_deposit, since it was deposited when the method execution started.

If the contract has any locked $NEAR, it will appear in account_locked_balance.

 

Storage Used

storage_used represents the amount of storage that is currently being used by your contract.

TIP: If you want to know how much storage a structure uses, print the storage before and after storing it.

 

Telling the Time

The environment exposes three different ways to tell the pass of time, each representing a different dimension of the underlying blockchain.

 

Timestamp

The timestamp attribute represents the approximated UNIX timestamp at which this call was executed. It quantifies time passing in a human way, enabling to check if a specific date has passed or not.

 

Current Epoch

The NEAR blockchain groups blocks in Epochs. The current_epoch attribute measures how many epochs have passed so far. It is very useful to coordinate with other contracts that measure time in epochs, such as the validators.

 

Block Index

The block_index represents the index of the block in which this transaction will be added to the blockchain.

 

Gas

Your contract has a limited number of computational resources to use on each call. Such resources are measured in Gas.

Gas can be thought of as wall time, where 1 PetaGas (1_000 TGas) is ~1 second of compute time.

Each code instruction costs a certain amount of Gas, and if you run out of it, the execution halts with the error message Exceeded the prepaid gas.

The environment gives you access to two gas-related arguments: prepaid_gas and used_gas.

 

Prepaid Gas

prepaid_gas represents the amount of Gas the predecessor attached to this call. It cannot exceed the limit 300TGas (300 * 1012 Gas).

 

Used Gas

used_gas contains the amount of Gas that has been used so far. It is useful to estimate the Gas cost of running a method.

DANGER: During cross-contract calls always make sure the callback has enough Gas to fully execute.

TIP: If you already estimated the Gas a method needs, you can ensure it never runs out of Gas by using assert

  • Rust

const REQUIRED_GAS: Gas = Gas(20_000_000_000_000); // 20 TGasassert!(env::prepaid_gas() >= REQUIRED_GAS, "Please attach at least 20 TGas");

 

 

Environment Functions

  • AssemblyScript

 
 

Function Name

 

SDK method

 

Description

 

SHA 256

context.sha256(value)

Hashes a sequence of bytes using sha256.

Keccak 256

context.keccak256(value)

Hashes a sequence of bytes using keccak256.

Keccak 512

context.keccak512(value)

Hashes a sequence of bytes using keccak512.

Panic String

context.panic(message)

Terminates the execution of the program with the UTF-8 encoded message.

Log String

context.log_utf8(message)

Logs the string message. This message is stored on chain.

Validator Stake

context.validator_stake(account_id)

For a given account return its current stake. If the account is not a validator, returns 0.

Validator Total Stake

context.validator_total_stake()

Returns the total stake of validators in the current epoch.

Account ID Validation

context.isValidAccountID(account_id)

Validates an accountId string.

 

INFO: In the JS SDK, throw new Error("message") mimics the behavior of Rust’s env::panic_str("message").

State & Data Structures

Each contract has its own state (storage), which only they can modify but anyone can see.

A contract stores all its data in a key-value storage. This however is abstracted from you by the SDK through serialization.

INFO: Contracts pay for their storage by locking part of their balance. Currently it costs ~1 Ⓝ to store 100KB

 

Defining the State

The contract’s state is defined by the main class attributes, and accessed through them.

In the state you can store constants, native types, and complex objects. When in doubt, prefer to use SDK collections over native ones, because they are optimized for the serialized key-value storage.

  • AssemblyScript

storage-as/contract/assembly/index.ts

loading...

 

See full example on GitHub

 

Data Structures

The NEAR SDK exposes a series of structures (Vectors, Sets, Maps and Trees) to simplify storing data in an efficient way.

INSTANTIATION

All structures need to be initialized using a unique prefix, which will be used to identify the structure’s keys in the serialized state

  • AssemblyScript

storage-as/contract/assembly/index.ts

loading...

 

See full example on GitHub

 

Vector

Implements a vector/array which persists in the contract’s storage. Please refer to the Rust and AS SDK’s for a full reference on their interfaces.

  • AssemblyScript

  • vector.ts

  • index.ts

storage-as/contract/assembly/__tests__/vector.spec.ts

loading...

 

See full example on GitHub

 

Map

Implements a map/dictionary which persists in the contract’s storage. Please refer to the Rust and AS SDK’s for a full reference on their interfaces.

  • AssemblyScript

  • map.ts

  • index.ts

storage-as/contract/assembly/__tests__/map.spec.ts

 

See full example on GitHub

Nesting of Objects – Temporary Solution

 

Set

Implements a set which persists in the contract’s storage. Please refer to the Rust and AS SDK’s for a full reference on their interfaces.

  • AssemblyScript

  • map.ts

  • index.ts

storage-as/contract/assembly/__tests__/set.spec.ts

loading...

 

See full example on GitHub

 

Tree

An ordered equivalent of Map. The underlying implementation is based on an AVL. You should use this structure when you need to: have a consistent order, or access the min/max keys.

  • AssemblyScript

  • tree.ts

  • index.ts

storage-as/contract/assembly/__tests__/tree.spec.ts

 

See full example on GitHub

 

Storage Cost

Your contract needs to lock a portion of their balance proportional to the amount of data they stored in the blockchain. This means that:

  • If more data is added and the storage increases ↑, then your contract’s balance decreases ↓.

  • If data is deleted and the storage decreases ↓, then your contract’s balance increases ↑.

Currently, it cost approximately 1 Ⓝ to store 100kb of data.

INFO: You can save on smart contract storage if using NEAR Account IDs by encoding them using base32. Since they consist of [a-z.-_] characters with a maximum length of 64 characters, they can be encoded using 5 bits per character, with terminal \0. Going to a size of 65 5 = 325 bits from the original (64 + 4) 8 = 544 bits. This is a 40% reduction in storage costs.

Now move to part 3 of this tutorial