--- tags: nc-programs title: Rust Contract Basics description: This course is an opportunity for web developers to earn a Certificate of Completion that represents the ability to design, develop, test and deploy smart contracts on the NEAR platform. image: https://near.org/wp-content/themes/near-19/assets/downloads/near_logo.svg GA: UA-188414449-3 --- # NEAR Contract Basics (Rust) > _as [presented by Evgeny Kuzyakov](https://www.youtube.com/watch?v=BA7VeUS_RAA&list=PL9tzQn_TEuFXnYksuNJwrl1l_AuWzn6eF) to the NEAR collective, Oct 2020_ ## Background The [voting contract](https://github.com/near/core-contracts/tree/master/voting) was used by MainNet validators to enable transfers in October 2020. This is significant because it made the network fully decentralized and [moved the NEAR network from phase I to phase II](https://near.org/blog/near-mainnet-phase-2-unrestricted-decentralized/) > The transition to Phase II occurred because NEAR Protocol’s validators > indicated via an on-chain voting mechanism that they believe the network is > sufficiently secure, reliable and decentralized to remove transfer restrictions > and officially allow it to operate at full functionality. Accounts that are > subject to lockup contracts will now begin their unlocking timeline. > > The shift to Phase II means 3 important things: > > 1. **Permissionlessness:** > With the removal of transfer restrictions, it is now possible for anyone to > transfer NEAR tokens and participate in the network. Specifically, it is now > possible for anyone to send or receive tokens, to create accounts, to > participate in validation, to launch applications or to otherwise use the > network … all without asking for anyone’s permission. This means individuals, > exchanges, defi contracts or anyone else can utilize the NEAR token and the NEAR > network in an unrestricted fashion. > > 2. **Voting:** The community-operated > NEAR Protocol successfully performed its first on-chain vote, indicating that > community governance is operational and effective. The exact mechanism will > change going forward but this is a substantial endorsement of the enthusiasm of > the community to participate. > 3. **Decentralization:** The dozens of > validators who participated in the vote were backed by over 120 million tokens > of delegated stake from over a thousand individual tokenholders and this vote > indicates that they believe the network is sufficiently secure and decentralized > to operate freely. > > In essence, NEAR is now fully ready to build on, ready > to use, and ready to grow to its full potential. While Bitcoin brought us Open > Money and Ethereum evolved that into the beginnings of Open Finance, we finally > have access to a platform with the potential to bridge the gap to a truly Open > Web. Evgeny walked us through the contract code in detail. This article attempts to capture the most salient points for developers new to the NEAR ecosystem who want to learn to write contracts for NEAR protocol using the Rust programming language. NEAR also supports AssemblyScript as a contract development langauge and future articles will attempt similar treatment for that language. _At time of writing, **NEAR recommends using Rust for high value contracts** which manage mission-critical features or significant levels of capital due to the maturity of the Rust language, its community and the vast amount of attention paid to the Rust-to-WebAssembly toolchain._ ## Filesystem The voting contract can be found here: https://github.com/near/core-contracts/tree/master/voting The key files in the repository are listed below and will be introduced in this document. ```shell voting ├── Cargo.toml └── src └── lib.rs ``` **A brief note on compiling contracts for NEAR** A NEAR contract written in Rust must be compiled as a library. This is in contrast to an executable, the kind of program that runs directly on your computer. On NEAR, contracts run inside of a virtual machine called the NEAR VM that is optimized to work with the blockchain. Contracts require another program, this VM, in order to execute. Technically speaking, there is no `wasm32-wasi` target as with typical Rust programs that run directly on the operating system. Instead, Rust smart contracts targeting NEAR use the `wasm32-unknown-unknown` interface. This creates a file that any runner (like NEAR VM, for example with Wasi support) can execute as if it was a normal binary. If you try to compile a contract for NEAR and your Rust toolchain is missing the Wasm components, the compiler will report an error similar to this one: ``` error[E0463]: can't find crate for `core` | = note: the `wasm32-unknown-unknown` target may not be installed ``` Run this command to add the Wasm target to the Rust toolchain: ``` rustup target add wasm32-unknown-unknown ``` ## Boilerplate To develop a NEAR contract in Rust there are a few things you will always do -- the boilerplate. **`Cargo.toml`** Familiar to Rust devs, this file specifies dependencies and other project flags including: - optimizations to improve size and performance of code - no debugging (this reduces size of code) - panic aborts (it's important that contracts fail loudly) - overflow checks that forces panic on lower / upper bound overflows in Wasm. silent overflows can lead to contract abuse so this increases safety ```ini= [package] name = "voting-contract" # Build a Wasm file called voting-contract [dependencies] near-sdk = "2.0.0" # Use near-sdk-rs version 2.0.0 [profile.release] opt-level = "s" # Optimize for small code size lto = true # Optimize for small code size debug = false # Do not include debug info panic = "abort" # Terminate process on panic overflow-checks = true # Panic on overflow ``` :::spoiler Click to view reference links for the above * `opt-level = "s"` [Optimize for small code size](https://doc.rust-lang.org/cargo/reference/profiles.html?highlight=lto#opt-level) * `lto = true` [Optimize for small code size](https://doc.rust-lang.org/cargo/reference/profiles.html?highlight=lto#lto) * `debug = false` [Do not include debug info](https://doc.rust-lang.org/cargo/reference/profiles.html?highlight=lto#debug) * `panic = "abort"` [Terminate process on panic](https://doc.rust-lang.org/cargo/reference/profiles.html?highlight=lto#panic) * `overflow-checks = true` [Panic on overflow](https://doc.rust-lang.org/cargo/reference/profiles.html?highlight=lto#overflow-checks) ::: --- ## Contract **`src/lib.rs`** :::spoiler Click to view the full voting contract (`src/lib.rs` ) <br> *As of [July 27, 2020 - `ac06ac4`](https://github.com/near/core-contracts/blob/ac06ac47a19de4048e4354cac77e1dfd1f73373e/voting/src/lib.rs) (excluding tests)* ```rust= use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::json_types::{U128, U64}; use near_sdk::{env, near_bindgen, AccountId, Balance, EpochHeight}; use std::collections::HashMap; #[global_allocator] static ALLOC: near_sdk::wee_alloc::WeeAlloc = near_sdk::wee_alloc::WeeAlloc::INIT; type WrappedTimestamp = U64; /// Voting contract for unlocking transfers. Once the majority of the stake holders agree to /// unlock transfer, the time will be recorded and the voting ends. #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize)] pub struct VotingContract { /// How much each validator votes votes: HashMap<AccountId, Balance>, /// Total voted balance so far. total_voted_stake: Balance, /// When the voting ended. `None` means the poll is still open. result: Option<WrappedTimestamp>, /// Epoch height when the contract is touched last time. last_epoch_height: EpochHeight, } impl Default for VotingContract { fn default() -> Self { env::panic(b"Voting contract should be initialized before usage") } } #[near_bindgen] impl VotingContract { #[init] pub fn new() -> Self { assert!(!env::state_exists(), "The contract is already initialized"); VotingContract { votes: HashMap::new(), total_voted_stake: 0, result: None, last_epoch_height: 0, } } /// Ping to update the votes according to current stake of validators. pub fn ping(&mut self) { assert!(self.result.is_none(), "Voting has already ended"); let cur_epoch_height = env::epoch_height(); if cur_epoch_height != self.last_epoch_height { let votes = std::mem::take(&mut self.votes); self.total_voted_stake = 0; for (account_id, _) in votes { let account_current_stake = env::validator_stake(&account_id); self.total_voted_stake += account_current_stake; if account_current_stake > 0 { self.votes.insert(account_id, account_current_stake); } } self.check_result(); self.last_epoch_height = cur_epoch_height; } } /// Check whether the voting has ended. fn check_result(&mut self) { assert!( self.result.is_none(), "check result is called after result is already set" ); let total_stake = env::validator_total_stake(); if self.total_voted_stake > 2 * total_stake / 3 { self.result = Some(U64::from(env::block_timestamp())); } } /// Method for validators to vote or withdraw the vote. /// Votes for if `is_vote` is true, or withdraws the vote if `is_vote` is false. pub fn vote(&mut self, is_vote: bool) { self.ping(); if self.result.is_some() { return; } let account_id = env::predecessor_account_id(); let account_stake = if is_vote { let stake = env::validator_stake(&account_id); assert!(stake > 0, "{} is not a validator", account_id); stake } else { 0 }; let voted_stake = self.votes.remove(&account_id).unwrap_or_default(); assert!( voted_stake <= self.total_voted_stake, "invariant: voted stake {} is more than total voted stake {}", voted_stake, self.total_voted_stake ); self.total_voted_stake = self.total_voted_stake + account_stake - voted_stake; if account_stake > 0 { self.votes.insert(account_id, account_stake); self.check_result(); } } /// Get the timestamp of when the voting finishes. `None` means the voting hasn't ended yet. pub fn get_result(&self) -> Option<WrappedTimestamp> { self.result.clone() } /// Returns current a pair of `total_voted_stake` and the total stake. /// Note: as a view method, it doesn't recompute the active stake. May need to call `ping` to /// update the active stake. pub fn get_total_voted_stake(&self) -> (U128, U128) { ( self.total_voted_stake.into(), env::validator_total_stake().into(), ) } /// Returns all active votes. /// Note: as a view method, it doesn't recompute the active stake. May need to call `ping` to /// update the active stake. pub fn get_votes(&self) -> HashMap<AccountId, U128> { self.votes .iter() .map(|(account_id, stake)| (account_id.clone(), (*stake).into())) .collect() } } ``` ::: --- In general, keep in mind that we are operating in the resource-contrained environment of the NEAR Protocol Runtime. For any single contract method invocation, all storage and compute operations must fit comfortably within a single block time of 1 second along with any other calls made on other contracts. It's useful to keep this in mind as we design contracts which operate as good and performant citizens of the network. You can read more about "[thinking in gas]" in our docs. Also surprising for some new to contract development: the concepts of randomness and time are not available on the same terms as when running on bare metal. Randomness is especially tricky and the only real authority on time in this context is the blockchain so any notion of time here is tied directly to epochs, specifically `EpochHeight`. The Rust SDK `near-sdk-rs` also exposes `env::random_seed`. Let's put these ideas aside for the time being and focus on the code of this contract. [thinking in gas]: https://docs.near.org/docs/concepts/gas#thinking-in-gas ### Line 1 `near_sdk_rs` provides support for writing performant contracts including a custom binary serialization / deserialization format called [Borsh] for storing contract state efficiently on chain to reduce both [storage] and [gas] costs. The first line of the contract brings these tools into scope. [Borsh]: https://borsh.io [storage]: https://docs.near.org/docs/concepts/storage#other-ways-to-keep-costs-down [gas]: https://docs.near.org/docs/concepts/gas ```rs=1 use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; ``` ### Line 2 Reading on, clients of the contract may require a JSON format for arguments & return types. Since JSON is limited to JavaScript number support (10^53), we need to wrap Rust u64 and u128 with some helper functionality that returns these numbers as JSON strings. Notice the difference in the uppercase "U" on wrapped types vs the lowercase "u" on Rust native types. ```rs=2 use near_sdk::json_types::{U128, U64}; ``` ### Line 3 `near-sdk-rs` exposes the virtual machine execution context through `env` which developers can use to interrogate the NEAR Runtime for information like the signer of the current transaction, the state of the contract, and more. `near_bindgen` will be explained where used lower in this document. `AccountId`, `Balance` and `EpochHeight` are all types that are native to NEAR protocol and represent what you expect them to. Check out the nearcore source code for more details about these types like [AccountId]. [AccountId]: https://github.com/near/nearcore/blob/master/core/primitives/src/types.rs#L15 ```rs=3 use near_sdk::{env, near_bindgen, AccountId, Balance, EpochHeight}; ``` ### Line 4 When choosing to organize how data is stored by your contract, it's important to first decide whether you want to use "Key-Value Storage" or "Singleton Storage". **Key-Value Storage** is a key-value structure which is ideal for managing large contract state or cases with sparse access to contract data (ie. you only need a few pieces of data on occasion). This state is only deserialized when accessed and otherwise remains untouched. The contract will access storage directly using the `Storage` interface and/or choose a more appropriate abstraction from among the list of available [collections], like a [PersistentVector] or [UnorderedMap]. **Singleton Storage** is a serialized representation of the contract struct. Using this type of state storage is more efficient when contract data is: 1. small and known to never risk growing unprocessably large, and 2. when read operations on the data are frequent Since this state is deserialized in its entirety every time a contract method is called, it's important to keep it small. [collections]: https://github.com/near/near-sdk-rs/tree/master/near-sdk/src/collections [PersistentVector]: https://github.com/near/near-sdk-rs/blob/master/near-sdk/src/collections/vector.rs [UnorderedMap]: https://github.com/near/near-sdk-rs/blob/master/near-sdk/src/collections/unordered_map.rs The choice of `std::collections::HashMap` here hints at a Singleton Storage strategy. ```rs=4 use std::collections::HashMap; ``` Pressing on ... ### lines 6 - 7 Rust allows us to define our own custom memory allocation mechanism to make memory management as efficient as possible. [`wee_alloc`](https://github.com/rustwasm/wee_alloc), the "Wasm-Enabled, Elfin (small) Allocator" has good performance when compiled to Wasm . In contrast, `wee_alloc` would be a poor choice for a scenario where allocation is a performance bottleneck. It's small, less than a kilobyte, but doesn't have as good performance as the standard allocator. In fact it in O(n) is the number of allocated objects. It's safe to consider these lines "boilerplate" and just include them in your contracts without much thought. If the choice of allocator changes for some reason, you'll probably hear about it. ```rs=6 #[global_allocator] static ALLOC: near_sdk::wee_alloc::WeeAlloc = near_sdk::wee_alloc::WeeAlloc::INIT; ``` ### Line 9 As mentioned earlier, `near-sdk-rs` provides wrapped types that allow contracts to return JSON-compatible formats for big numbers. These types are required when contract methods return large numbers to a JavaScript execution context like the browser. Internal calls ignore these types and use the native Rust types as usual. In the following case, the original "Something" is of type `u64` but we want to make sure that we can serialize and deserialize it at the contract boundary when communicating with the outside world. So we "wrap" it by using the type `U64` with uppercase "U", the same type but with conveniently automated serialization (a requirement for any contract method arguments). ```rs=9 type WrappedSomething = U64; ``` ### Lines 13 - 15 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 preceded with this macro since it exposes the execution environment to the contract which allows it to receive method calls, send results and generally behave like a contract within the Runtime environment. Each contract has its own Singleton Storage as part of the contract account where contract state will be serialized and stored on chain. Inspecting account storage will reveal a key named `STATE` whose value is this Singleton Storage. The following lines define the struct which will become this Singleton representation of the contract. Only one public struct should be decorated with the `near_bindgen` macro, along with the implementation of that same struct. Contracts are efficiently serialized using Borsh, the binary representation described earlier in this document. [generates]: https://github.com/near/near-sdk-rs/blob/master/near-sdk-macros/src/lib.rs#L13 ```rs=13 #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize)] pub struct SomeContract { /* code removed for clarity */ } ``` ### Lines 16 - 23 Within the contract `struct` we can store any valid Rust type. This contract includes an example of storing a map of custom types with the `HashMap` (specifically mapping `AccountId`s to some related `Balance`). It also includes an example of storing a custom type exposed by `near-sdk-rs` called `Balance`, an `Option` type (meaning it may or may not have a value) and the `EpochHeight` for keeping track of "time" from within this context (ie. while running on the blockchain as mentioned above). ```rs=16 /// How much each validator votes votes: HashMap<AccountId, Balance>, /// Total voted balance so far. total_voted_stake: Balance, /// When the voting ended. `None` means the poll is still open. result: Option<WrappedTimestamp>, /// Epoch height when the contract is touched last time. last_epoch_height: EpochHeight, ``` ### Lines 26 - 30 This bit of code is part of a larger context where we have decided to control contract initialization by decorating a specific method with the `#[init]` macro (see below). By default all contract methods will try to initialize contract state if not already initialized (since it's possible to call any public methods on a deployed contract) but in this case we want to avoid that behavior. ```rs=26 impl Default for VotingContract { fn default() -> Self { env::panic(b"VotingContract should be initialized before usage") } } ``` ### Lines 32 - 33 Decorating an implementation section (`impl`) with `#[near_bindgen]` wraps all public methods with some extra code that handles serialization / deserialization for method arguments and return types, panics if money is attached unless the method is decorated with `#[payable]`, and [more](https://github.com/near/near-sdk-rs/blob/08bd75562d55c5cd6a692456b6745b6f594c18b6/near-sdk-macros/src/lib.rs#L13). ```rs=32 #[near_bindgen] impl VotingContract { /* code removed for clarity */ } ``` ### Lines 34 - 43 Decorating a public method with `#[init]` will add some machinery to "initialize the contract" by serializing and saving the returned value (in this case a `struct`) into the contract's Singleton Storage. You can add an `#[init]` decorator to any public method to mark it as something like a "constructor" for the contract -- note that there's nothing special about the `new()` method name here, it could just as well be `old()` and would work fine. `new()` is a convention used by the NEAR community because it sets the correct expectation for anyone reading the code. This implementation references `env::state_exists()` to assert that the contract's Singleton Storage state has not been initialized, ie. it does not already exist. We don't want to accidentally re-initialize the contract at some later time and blow away any valuable state that has been captured! The `#[init]` macro expects that the method it decorates will return an instance of the `struct` which is being referenced by this implementation section. The return value will be serialized on chain into the contract's Singleton Storage. If you're new to Rust then you may not be familiar with "implicit returns", a language feature likely passed down through the influence of Ruby. It allows the last line of a method to be assigned as the method's return value. In Rust we get an implicit return of the value of the last line of a method if we just ignore the semicolon at the end of the last expression. In this case the last line returns an instance of the `VotingContract` with all attributes of the `struct` initialized to their default values. Rust calls these "expressions" rather than "implicit returns" (https://doc.rust-lang.org/book/ch03-03-how-functions-work.html#function-bodies-contain-statements-and-expressions) Note that the method signature of `new()` is returning an instance of `Self` but could just as well have been `VotingContract` except this way we will have to change fewer tokens in the code if we ever decide to rename the contract. A modest but meaningful gift to our future selves, this is. ```rs=34 #[init] pub fn new() -> Self { assert!(!env::state_exists(), "The contract is already initialized"); VotingContract { votes: HashMap::new(), total_voted_stake: 0, result: None, last_epoch_heigh: 0, } } ``` ## Other Features of `near-sdk-rs` Although the following lines are not specific to the `Voting` contract, it may be instructive to cover these while we're thinking about the basics of contract design for NEAR Protocol. These features are available to contract developers using Rust on NEAR Protocol. ### Public methods Public methods (`pub fn`) will be exposed as contract methods avaliable for clients to call. ```rs= // public methods will be exposed as contract methods avaliable for clients to call pub fn do_something() { // unimplemented!(); } ``` ### Borrowing `&self` If a method borrows self -- that is, the first argument in the list is `&self`, where the `&` is a Rust modifier that basically means "safely take a temporary reference to ..." -- then you can be sure that it will *interact* with contract state but will NOT CHANGE contract state. ```rs= pub fn do_something_else(&self) { // unimplemented!(); } ``` ### Borrowing `&mut self` Similar to the above, borrowing a *mutable* reference means the method will *interact* with contract state and WILL CHANGE contract state. ```rs= pub fn do_something_else_entirely(&mut self) { // unimplemented!(); } ``` Going a level deeper here, it may be worth noting at this point that `#[near_bindgen]` actually adds code to *each* method of the contract which reacts to the presence of `self` or `&mut self`. This additional code first tries to deserialize Singleton Storage, ie. contract state. Recall that this Singleton contract state is stored as the value associated with a reserved key named `STATE`. `near-sdk-rs` will call `Default()` if `STATE` does not exist (detected by a call to `env::state_exists()`), otherwise it will call `Default()` as part of preparing to run a method. If you're familiar with Rust macros, `#[near_bindgen]` is very readable. If not, the author found that it helps to alternate between holding your breath and taking slow, deep breaths: counting 4 in then 8 out while staring at it for a good long while. ### Argument and return types Methods can take arguments which will be deserialized from JSON and return types which will be serialized to JSON if called from an external context. Making internal method calls within a Rust context doesn't require the same serialization / deserialization overhead. Internally we can call contracts using positional arguments. Via any external interface (simulation tests, `near-api-js`, etc), we have to pass JSON objects which get deserialized into the function arguments as needed. In summary, if you call a method directly without `Promise` or `ext_contract` API then there is no serialization / deserialization happening. If you call from a frontend using `near-api-js` or raw RPC, or using the cross-contract API, then serialization is needed. ```rs= pub fn do_one_more_thing(some_bool: bool) -> Option<SomeType>{ // unimplemented!(); } ``` ### Payable methods If you want to attach (send) tokens to a contract method, add `#[payable]`. Decorating a contract method with the `#[payable]` macro will allow the method to accept attached native NEAR tokens without a panic. If tokens are attached to a method call *without* adding the `#[payable]` macro then the method will panic. ```rs= #[payable] pub fn show_me_the_money() { let tokens = env::attached_deposit() } ``` ### Private methods If you want to hide methods from the contract's public interface, don't make them public. Methods which are not marked as public will not be exposed as part of the contract interface ```rs= fn private_method(){ unimplemented!(); } ``` ### Protected methods If you want to maintain restricted access to some methods so that only the *contract account itself* may call them, add `#[private]`. Sometimes a method should be available to the contract via promise call which requires that it is exposed on the contract but not callable by anyone other than the contract itself. This is a common pattern in NEAR which can be hand-crafted with a few lines of code but this macro makes it more readable (see `near-sdk-rs` readme: https://github.com/near/near-sdk-rs/blob/master/README.md) (*NOTE: this is a pending feature not available in `near-sdk-rs` 2.0.0 at time of writing*) ```rs= #[private] pub fn contract_private_method(){ // unimplemented!(); } ``` ## A Brief Note on Contract Design Beyond the basics of writing contracts for NEAR using Rust, there are a few critical design issues to be aware of as a contract author. These concerns stem from a single attribute of smart contracts -- they are public interfaces that can be invoked by any valid transaction on the network. The implication is that we should be more defensive in our development of contracts and always assume the potential for malicious or adversarial behavior by bad actors. This defensive stance will lead to safer code and more robust contracts. (1) For sensitive methods which are modifying contract state, it's important to check `env::predecessor_account_id()` to prevent being called by the wrong account maliciously. In general, having a `setter()` method that does not verify the callers authority or ownership before applying changes to contract state is an antipattern. (2) All contract data is essentially public information and can be queried via the NEAR RPC API at will for free. This implies that any information stored by the contract should not open the contract for exploitation by bad actors.