[Announcement] near-contract-tools - functions and macros for developing Rust smart contracts on NEAR

Introducing: near-contract-tools

Helpful functions and macros for developing smart contracts on NEAR Protocol.

The goal of near-contract-tools is to provide secure-by-default, drop-in components to compose smart contracts for NEAR Protocol. near-contract-tools is a collection of common tools and patterns for NEAR smart contract development which make source code cleaner, easier to read, and more flexible.

Projects using near-contract-tools can:

  • write an NEP-141 fungible token in two lines of code,
  • fearlessly manage storage keys,
  • create NEP-297 events with a single macro invocation,
  • easily implement role-based permissioning, contract ownership, and pausability,
  • add highly customizable multisig functionality,
  • and more!

THIS IS STILL EARLY SOFTWARE: WE ARE LOOKING FOR PEOPLE WILLING TO TEST THIS LIBRARY IN PREPARATION FOR ITS 1.0 RELEASE.

Background

The NEAR Foundation formed the new Engineering team to provide in-house software developmentto accelerate the NEAR ecosystem. The Engineering team has been hard at work on near-contract-tools to support a wide-range of smart contract needs.

If near-sdk is the atoms of your smart contract, near-contract-tools is the molecules.

Design Principles

  • Developer experience. This library is written by Rust developers, for Rust developers, so the patterns are implemented in the way most natural for Rust.
  • "Just Works." Sane defaults; extra options are there if you want to dive deeper.
  • Modular. Although there is a FungibleToken composite macro, each standard (NEP-141, NEP-148) is implemented completely independently.
  • Non-invasive. You can add near-contract-tools to your project and just use one of the components: it doesn’t require that you use, for example, the Owner component in order to use the Migrate component. However, if you do want this functionality, it is simple to get the components to work together.
  • Concise. Macro usage is optional, but preferred.

Core Components

  • Approval
    • Multisig
  • NEP Standards
    • NEP-141 (Fungible Token Core)
    • NEP-148 (Fungible Token Metadata)
    • NEP-297 (Events)
  • State migration
  • Upgradability
  • Ownership
  • Pausability
  • Role-based access control
  • Storage slots calculated at compile time
  • (miscellaneous other utilities)

Installation

Add near-contract-tools to your Rust smart contract using cargo:

$ cargo add near-contract-tools

Examples

Fungible Token 2-liner

// add these lines:
#[derive(near_contract_tools::FungibleToken)]
#[fungible_token(name = "My Fungible Token", symbol = "MYFT", decimals = 24, no_hooks)]
// that's it!
#[near_sdk::near_bindgen]
struct MyFungibleTokenContract { }
// no need to put anything in the default struct

Use it:

#[near_sdk::near_bindgen]
impl MyFungibleTokenContract {
    pub fn send_half_my_balance_to(&mut self, account_id: near_sdk::AccountId) {
        // Nep141 functions are available to the public blockchain
        use near_contract_tools::standard::nep141::Nep141;

        // ft_* functions are defined!
        let balance: u128 = self.ft_balance_of(account_id.clone()).into();
        let send = balance / 2;
        near_sdk::require!(send > 0, "You don't have enough money!");
        self.ft_transfer(
            account_id,
            send.into(),
            Some("Half my balance!".to_string()),
        );
    }

    pub fn double_my_balance(&mut self) {
        // Nep141Controller functions are not available to the public blockchain
        use near_contract_tools::standard::nep141::{Nep141, Nep141Controller};

        let predecessor = near_sdk::env::predecessor_account_id();
        let balance = self.ft_balance_of(predecessor.clone());
        // Here's one of those non-public functions
        self.mint(&predecessor, balance.into(), Some("Doubling my balance!"));
    }
}

NEP-297 Events

use near_contract_tools::{event, standard::nep297::Event};
use near_sdk::{AccountId, json_types::U128};

// Definition
#[event(standard = "ft_event", version = "1.0.0")]
pub struct FtMint {
    pub owner_id: AccountId,
    pub amount: U128,
}

#[event(standard = "ft_event", version = "1.0.0")]
pub struct FtTransfer {
    pub old_owner_id: AccountId,
    pub new_owner_id: AccountId,
    pub amount: U128,
}

// ...

Use it:

FtMint {
    owner_id: "owner".parse().unwrap(),
    amount: 100.into(),
}
.emit();

Check out more example code.

Build contracts fast with near-contract-tools!

near-contract-tools improves the smart contract development experience on NEAR Protocol. It comes with macros that work out-of-the-box, providing developers with patterns that are often copy-pasted across projects. near-contract-tools is highly modular, and each component is opt-in. Developers can extend the components with custom functionality and use them alongside the rest of the library.

This is one more tool to help you build without limits!

4 Likes

Hey @encody great work on this project!

Can you talk about the differences between the multisig used in near-contract-tools as opposed to: core-contracts/multisig2 at master · near/core-contracts · GitHub.

Also could you explain storage slots being calculated at compile time. What are the major benefits and are there any security risks that developers should be aware of when using this storage slot pattern?

1 Like

The OpenZeppelin for the NEAR Dapps! Congrats @jacob !

2 Likes

@jacob This looks great! I think you could copy most or all of this post and put it in the Readme, too.

1 Like

The multisig implementation in near-contract-tools is split into a few parts. First, there’s the Approval component, which manages the approval process for and execution of actions. This makes it easy to write different approval schemes (e.g. n of m (provided), governance token holding, etc.) and different action types, which can then be mixed-and-matched. For the typical multisig wallet, an action would simply be any arbitrary transaction, and since this is a common use-case, near-contract-tools provides NativeTransactionAction, which is a thin wrapper around the native transaction action kinds.

Storage slots are a way to increase storage efficiency by calculating storage keys instead of storing them. You can explore the differences between these two contracts:

  • Fungible Token with near-contract-standards (note that this also implements the storage management standard, so it is not exactly a apples-to-apples comparison)
  • Fungible Token with near-contract-tools

The benefits of using storage slots:

  • Reduces storage I/O cycles, since STATE deserialization is not necessary
  • Reduces the amount of data put in storage
  • It is easier to find potential key collisions since all of the information needed to calculate a key is in the contract code and not split across the code and the storage