near-bindgen 0.3.10

Rust library for writing NEAR smart contracts.
Documentation

Example

Wrap a struct in #[near_bindgen] and it generates a smart contract compatible with the NEAR blockchain:

#[near_bindgen]
#[derive(Default, BorshDeserialize, BorshSerialize)]
pub struct StatusMessage {
    records: HashMap<String, String>,
}

#[near_bindgen]
impl StatusMessage {
    pub fn set_status(&mut self, message: String) {
        let account_id = env::signer_account_id();
        self.records.insert(account_id, message);
    }

    pub fn get_status(&self, account_id: String) -> Option<String> {
        self.records.get(&account_id).cloned()
    }
}

Features

  • Unit-testable. Writing unit tests is easy with near-bindgen:

    #[test]
    fn set_get_message() {
        let context = get_context(vec![]);
        testing_env!(context);
        let mut contract = StatusMessage::default();
        contract.set_status("hello".to_string());
        assert_eq!("hello".to_string(), contract.get_status("bob_near".to_string()).unwrap());
    }
    

    Run unit test the usual way:

    cargo test --package status-message
    
  • Asynchronous cross-contract calls. Asynchronous cross-contract calls allow parallel execution of multiple contracts in parallel with subsequent aggregation on another contract. env exposes the following methods:

    • promise_create -- schedules an execution of a function on some contract;
    • promise_then -- attaches the callback back to the current contract once the function is executed;
    • promise_and -- combinator, allows waiting on several promises simultaneously, before executing the callback;
    • promise_return -- treats the result of execution of the promise as the result of the current function.

    Follow examples/cross-contract-high-level to see various usages of cross contract calls, including system-level actions done from inside the contract like balance transfer (examples of other system-level actions are: account creation, access key creation/deletion, contract deployment, etc).

  • Initialization methods. We can define an initialization method that can be used to initialize the state of the contract.

    #[near_bindgen(init => new)]
    impl StatusMessage {
      pub fn new(user: String, status: String) -> Self {
          let mut res = Self::default();
          res.records.insert(user, status);
          res
      }
    }
    

Pre-requisites

To develop Rust contracts you would need to:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
  • Add wasm target to your toolchain:
rustup target add wasm32-unknown-unknown

Writing Rust Contract

You can follow the examples/status-message crate that shows a simple Rust contract.

The general workflow is the following:

  1. Create a crate and configure the Cargo.toml similarly to how it is configured in examples/status-message/Cargo.toml;

  2. Crate needs to have one pub struct that will represent the smart contract itself:

    • The struct needs to implement Default trait which NEAR will use to create the initial state of the contract upon its first usage;
    • The struct also needs to implement BorshSerialize and BorshDeserialize traits which NEAR will use to save/load contract's internal state;

    Here is an example of a smart contract struct:

    #[near_bindgen]
    #[derive(Default, BorshSerialize, BorshDeserialize)]
    pub struct MyContract {
        data: HashMap<u64, u64>
    }
    
  3. Define methods that NEAR will expose as smart contract methods:

    • You are free to define any methods for the struct but only public methods will be exposed as smart contract methods;
    • Methods need to use either &self, &mut self, or self;
    • Decorate the impl section with #[near_bindgen] macro. That is where all the M.A.G.I.C. (Macros-Auto-Generated Injected Code) is happening
    • If you need to use blockchain interface, e.g. to get the current account id then you can access it with env::*;

    Here is an example of smart contract methods:

    #[near_bindgen]
    impl MyContract {
       pub fn insert_data(&mut self, key: u64, value: u64) -> Option<u64> {
           self.data.insert(key)
       }
       pub fn get_data(&self, key: u64) -> Option<u64> {
           self.data.get(&key).cloned()
       }
    }
    

Building Rust Contract

We can build the contract using rustc:

RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release