4 |
5 |
9 |
12 | Your Site Title Here
13 |
14 |
15 | If you are not redirected automatically, follow this
16 | link.
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/theme/Root.js:
--------------------------------------------------------------------------------
1 | // https://docusaurus.io/docs/swizzling#wrapper-your-site-with-root
2 |
3 | import React from 'react';
4 | import useIsBrowser from '@docusaurus/useIsBrowser'; // https://docusaurus.io/docs/advanced/ssg#useisbrowser
5 | import Gleap from "gleap"; // See https://gleap.io/docs/javascript/ and https://app.gleap.io/projects/62697858a4f6850036ae2e6a/widget
6 |
7 | const GLEAP_API_KEY = 'K2v3kvAJ5XtPzNYSgk4Ulpe5ptgBkIMv';
8 |
9 | // Default implementation, that you can customize
10 | export default function Root({ children }) {
11 | const isBrowser = useIsBrowser();
12 | if (isBrowser) {
13 | Gleap.initialize(GLEAP_API_KEY);
14 | }
15 | return <>{children}>;
16 | }
17 |
--------------------------------------------------------------------------------
/sidebars.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Creating a sidebar enables you to:
3 | - create an ordered group of docs
4 | - render a sidebar for each doc of that group
5 | - provide next/previous navigation
6 |
7 | The sidebars can be generated from the filesystem, or explicitly defined here.
8 |
9 | Create as many sidebars as you want.
10 | */
11 |
12 | // @ts-check
13 |
14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
15 | const sidebars = {
16 | // By default, Docusaurus generates a sidebar from the docs folder structure
17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
18 |
19 | // But you can create a sidebar manually
20 | /*
21 | tutorialSidebar: [
22 | {
23 | type: 'category',
24 | label: 'Tutorial',
25 | items: ['hello'],
26 | },
27 | ],
28 | */
29 | };
30 |
31 | module.exports = sidebars;
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Website
2 |
3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
4 |
5 | ## Installation
6 |
7 | ```console
8 | yarn install
9 | ```
10 |
11 | ## Local Development
12 |
13 | ```console
14 | yarn start
15 | ```
16 |
17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
18 |
19 | ## Build
20 |
21 | ```console
22 | yarn build
23 | ```
24 |
25 | This command generates static content into the `build` directory and can be served using any static contents hosting service.
26 |
27 | ## Deployment
28 |
29 | ```console
30 | GIT_USER= USE_SSH=true yarn deploy
31 | ```
32 |
33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
34 |
--------------------------------------------------------------------------------
/mlc_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "ignorePatterns": [
3 | {
4 | "pattern": "^/"
5 | },
6 | {
7 | "pattern": "^http://127.0.0.1"
8 | },
9 | {
10 | "pattern": "^https://near.events"
11 | },
12 | {
13 | "pattern": "^https://etherscan.io"
14 | },
15 | {
16 | "pattern": "^https://crates.io"
17 | },
18 | {
19 | "pattern": "^https://ropsten.etherscan.io"
20 | },
21 | {
22 | "pattern": "^https://support.ledger.com"
23 | },
24 | {
25 | "pattern": "^https://help.github.com"
26 | },
27 | {
28 | "pattern": "^https://explorer.betanet.near.org"
29 | },
30 | {
31 | "pattern": "^https://rpc.testnet.near.org"
32 | }
33 | ],
34 | "timeout": "20s",
35 | "retryOn429": true,
36 | "retryCount": 5,
37 | "fallbackRetryDelay": "30s",
38 | "aliveStatusCodes": [200, 206]
39 | }
40 |
--------------------------------------------------------------------------------
/docs/building/reproducible-builds.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # Reproducible Builds
6 | Reproducible builds let different people build the same program and get the exact same outputs as one another. It helps users trust that deployed contracts are built correctly and correspond to the source code. To verify your contract user can build it themselves and check that the binaries are identical.
7 |
8 | ## Problem
9 | If you will build your contract on two different machines, most likely you will get two similar but not identical binaries. Your build artifact can be affected by the locale, timezone, build path, and billion other factors in your build environment. Rust community has a long story of fighting this issue but still, [it is not achieved yet](https://github.com/rust-lang/rust/labels/A-reproducibility).
10 |
11 | ## CI solution
12 | We recommend you to build your contracts with the use of our [Contract Builder](https://github.com/near/near-sdk-rs/tree/master/contract-builder). It's is using Docker, controlled and sharable environment that can be used by both you and your users. Docker image is available [here](https://hub.docker.com/r/nearprotocol/contract-builder). The contract built in it will result in a binary that is the same if built on other machines.
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sdk-docs",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "docusaurus": "docusaurus",
7 | "start": "docusaurus start",
8 | "build": "docusaurus build",
9 | "swizzle": "docusaurus swizzle",
10 | "deploy": "docusaurus deploy",
11 | "clear": "docusaurus clear",
12 | "serve": "docusaurus serve",
13 | "write-translations": "docusaurus write-translations",
14 | "write-heading-ids": "docusaurus write-heading-ids"
15 | },
16 | "dependencies": {
17 | "@docusaurus/core": "^2.0.0-beta.15",
18 | "@docusaurus/plugin-sitemap": "^2.0.0-beta.15",
19 | "@docusaurus/preset-classic": "^2.0.0-beta.15",
20 | "@docusaurus/plugin-ideal-image": "^2.0.0-beta.15",
21 | "@mdx-js/react": "^1.6.21",
22 | "@saucelabs/theme-github-codeblock": "^0.1.1",
23 | "clsx": "^1.1.1",
24 | "gleap": "^7.0.29",
25 | "hast-util-is-element": "1.1.0",
26 | "prism-react-renderer": "^1.2.1",
27 | "react": "^17.0.1",
28 | "react-dom": "^17.0.1",
29 | "url": "^0.11.0"
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.5%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/docs/contract-interface/payable-methods.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 4
3 | ---
4 |
5 | # Payable Methods
6 |
7 | We can allow methods to accept token transfer together with the function call. This is done so that contracts can define a fee in tokens that needs to be paid when they are used. By default the methods are not payable and they will panic if someone will attempt to transfer tokens to them during the invocation. This is done for safety reason, in case someone accidentally transfers tokens during the function call.
8 |
9 | To declare a method as payable, use the `#[payable]` annotation within the [`near_bindgen` macro](../contract-structure/near-bindgen.md) as follows:
10 |
11 | ```rust
12 | #[payable]
13 | pub fn my_method(&mut self) {
14 | ...
15 | }
16 | ```
17 |
18 | This will allow the `my_method` function to be called and transfer balance to the contract.
19 |
20 | Example:
21 |
22 | ```rust
23 | #[near_bindgen]
24 | impl Contract {
25 | #[payable]
26 | pub fn take_my_money(&mut self) {
27 | near_sdk::env::log_str("Thanks!");
28 | }
29 | pub fn do_not_take_my_money(&mut self) {
30 | near_sdk::env::log_str("Thanks!");
31 | }
32 | }
33 | ```
34 |
35 | is equivalent to:
36 |
37 | ```rust
38 | #[near_bindgen]
39 | impl Contract {
40 | pub fn take_my_money(&mut self) {
41 | near_sdk::env::log_str("Thanks!");
42 | }
43 | pub fn do_not_take_my_money(&mut self) {
44 | if near_sdk::env::attached_deposit() != 0 {
45 | near_sdk::env::panic_str("Method do_not_take_my_money doesn't accept deposit");
46 | }
47 | near_sdk::env::log_str("Thanks!");
48 | }
49 | }
50 | ```
51 |
--------------------------------------------------------------------------------
/docs/promises/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | sidebar_label: Introduction
4 | pagination_label: "Promises: Introduction"
5 | ---
6 |
7 | # Promises
8 |
9 | Transactions can be sent asynchronously from a contract through a [`Promise`](https://docs.rs/near-sdk/latest/near_sdk/struct.Promise.html). Like Promises in many programming languages, these will cause code to be executed in the future. In the case of NEAR, this "in the future" means a transaction to be executed _in the next block_ (or thereabouts), rather than in the same block as the original function call.
10 |
11 | You can implement any cross-contract workflow using Promises; they inhabit a middle-ground between the high-level and low-level approaches discussed in [the last section](../cross-contract/callbacks.md). See the full Promise docs, linked above, for details.
12 |
13 | However, there are a few situations where Promises are uniquely capable, since these situations don't involve making function calls:
14 |
15 | * Sending $NEAR
16 | * Creating accounts
17 | * Deploying contracts
18 |
19 | :::info Why wait?
20 | Why not do these things synchronously, in the same block when the function is called? Why does NEAR require a `Promise` for sending tokens, or creating an account, or deploying a contract?
21 |
22 | They need to be scheduled in separate blocks since sender and receiver accounts can be on different shards, and cross-shard communication happens across blocks by passing receipts (you can think of receipts in NEAR as "internal transactions"). You can see these receipts being passed from block to block [in NEAR Explorer](https://explorer.near.org/transactions/36n3tBNiF497Tm9mijEpsCUvejL8mBYF1CEWthCnY8FV).
23 | :::
--------------------------------------------------------------------------------
/static/img/near_logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/img/near_logo_white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/building/post-processing-tools.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | ---
4 |
5 | # Post Processing Tools
6 | The size of the contract is a critical characteristic. The best way to keep it small is a well-designed minimalistic code with a reduced number of dependencies. It is especially important for large contracts and huge multi-contract dApps that can take a fortune to deploy.
7 |
8 | When you have done your best with the code optimization it is worth reducing the size of the contract by minifying it.
9 |
10 | ## Ready to use script
11 | We have prepared a simple `bash` script that can be used to minify `.wasm` contract file. You can find it [here](https://github.com/near/near-sdk-rs/blob/master/minifier/minify.sh).
12 |
13 | The current approach to minification is the following:
14 | 1. Snip (i.e. just replace with unreachable instruction) few known fat functions from the standard library (such as float formatting and panic-related) with `wasm-snip`.
15 | 2. Run `wasm-gc` to eliminate all functions reachable from the snipped functions.
16 | 3. Strip unneeded sections, such as names with `wasm-strip`.
17 | 4. Run `binaryen wasm-opt`, which cleans up the rest.
18 |
19 | ### Requirements to run the script:
20 | - install [wasm-snip](https://docs.rs/wasm-snip/0.4.0/wasm_snip/) and [wasm-gc](https://docs.rs/crate/wasm-gc/0.1.6) with Cargo:
21 | ```bash
22 | cargo install wasm-snip wasm-gc
23 | ```
24 | - install [binaryen](https://github.com/WebAssembly/binaryen) and [wabt](https://github.com/WebAssembly/wabt) on your system. For Ubuntu and other Debian based Linux distributions run:
25 | ```bash
26 | apt install binaryen wabt
27 | ```
28 | ## WARNING
29 | Minification could be rather aggressive, so you must test the contract after minification. Standalone NEAR runtime could be helpful [here](https://github.com/nearprotocol/nearcore/tree/master/runtime/near-vm-runner).
--------------------------------------------------------------------------------
/docs/upgrading/via-dao-vote.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | ---
4 |
5 | # DAO-Governed Updates
6 |
7 | When you first deploy a contract to [mainnet](https://docs.near.org/concepts/basics/networks), you will likely keep control of a [Full Access key](https://docs.near.org/concepts/basics/accounts/access-keys) for the contract. This puts the contract in "trusted" mode, in which you and other maintainers can change it at-will (which means your users need to trust you to not steal their funds, change their votes, or otherwise behave maliciously). This is fine for early-stage contracts & apps, but like any blockchain, NEAR allows you to do better.
8 |
9 | When you're ready, you can remove all Full Access keys. This means no one will be able to unilaterally upgrade the contract. Instead, the contract will be upgradable only via a [DAO](https://whiteboardcrypto.com/what-is-a-dao/). Before you remove all Full Access keys, you implement two methods:
10 |
11 | 1. A method to store a proposed new version of the contract (as Wasm bytes, in an inspectable way so DAO members can verify that the bytes match a specific change to the source code). This function is safe, and could be called by anyone.
12 | 2. Another method to actually deploy a proposed new version. This method should check that it is being called by your DAO contract. The account name of the DAO could be set in your contract's storage with a field like `owner_id`, so that it is itself upgradable via the same process.
13 |
14 | Here's [how Ref Finance does this](https://github.com/ref-finance/ref-contracts/blob/b3aa78e83f2459017c9301d1f1b8d1ba8bcf6e7e/ref-exchange/src/owner.rs#L52-L107), [how SputnikDAO does it](https://github.com/near-daos/sputnik-dao-contract/blob/a8fc9a8c1cbde37610e56e1efda8e5971e79b845/sputnikdao2/src/types.rs#L74-L142), and some [other tips](https://hackmd.io/_UMem3SNSAeIqQASlRZahg).
15 |
16 | That's all we have for now! This page is a stub. Sorry about that. Can you help?
17 |
--------------------------------------------------------------------------------
/docs/building/basic-build.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Basic instructions
6 | To compile release version of the smart contract you can run:
7 |
8 | ```bash
9 | cargo build --target wasm32-unknown-unknown --release
10 | ```
11 |
12 | > **Note:** The above `build` command is setting a `target` flag to create a WebAssembly `.wasm` file.
13 |
14 | Notice that your project directory now has a few additional items:
15 |
16 | ```bash
17 | .
18 | ├── Cargo.lock ⟵ created during build to lock dependencies
19 | ├── Cargo.toml
20 | ├── src
21 | │ └── lib.rs
22 | └── target ⟵ created during build, holds the compiled wasm
23 | ```
24 | # Build and Flags
25 | We recommend you to optimize your build artifact with the use of the next flags in your Cargo.toml file. If you are performing a multi-contract build, you should include these settings in the Cargo.toml that is at the root of your project.
26 |
27 | ```bash
28 | [profile.release]
29 | codegen-units = 1
30 | # Tell `rustc` to optimize for small code size.
31 | opt-level = "z"
32 | lto = true
33 | debug = false
34 | panic = "abort"
35 | # Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
36 | overflow-checks = true
37 | ```
38 |
39 | The above command is essentially setting special flags and optimizing the resulting `.wasm` file. At the end of the day, this allows you to customize the `cargo build --release` command.
40 |
41 | # Custom Flags
42 | If you wish to add custom flags to your build, you can perform this by adding build flags to your `ProjectFolder/.cargo/config.toml` as illustrated in this example.
43 |
44 | ```toml
45 | [target.wasm32-unknown-unknown]
46 | rustflags = ["-C", "link-arg=-s"]
47 | ```
48 |
49 | A full set of build options can be accessed at https://doc.rust-lang.org/cargo/reference/config.html.
50 |
51 |
52 | You can find an example [here](https://github.com/near/near-sdk-rs/blob/05e4539a8f3db86dd43b768ee9660dd4c8e7ea5c/examples/fungible-token/.cargo/config.toml).
53 |
--------------------------------------------------------------------------------
/docs/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | slug: /
4 | ---
5 |
6 | # Getting Started
7 |
8 | ## Install Rust and Wasm toolchain
9 |
10 | Follow [these instructions](https://doc.rust-lang.org/book/ch01-01-installation.html) for setting up Rust.
11 |
12 | To install Rust on Linux or MacOS, use the following command:
13 |
14 | ```bash
15 | curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
16 |
17 | source $HOME/.cargo/env
18 | ```
19 |
20 | Then, add the `wasm32-unknown-unknown` toolchain. This toolchain is required because the contracts that we will build will be compiled to [Wasm](https://webassembly.org/) to run on the NEAR blockchain.
21 |
22 | ```bash
23 | rustup target add wasm32-unknown-unknown
24 | ```
25 |
26 | ## Create a new project
27 |
28 | The best way to create a new NEAR app connected with a frontend is through [create-near-app](https://github.com/near/create-near-app). When initializing the project, be sure to include the `--contract=rust` flag to use the Rust SDK. Add `--frontend=react` to use react. Default is vanilla HTML.
29 |
30 | ```bash
31 | npx create-near-app --contract=rust my-project
32 | ```
33 |
34 | If you only wish to develop and deploy a Rust contract, the [status message example](https://github.com/near-examples/rust-status-message) is great to use as a template or through [cargo-generate](https://github.com/cargo-generate/cargo-generate).
35 |
36 | To initialize a new project with `cargo-generate`, run the following commands:
37 |
38 | ```bash
39 | cargo install cargo-generate --features vendored-openssl
40 |
41 | cargo generate --git https://github.com/near-examples/rust-status-message --name my-project
42 | cd my-project
43 | ```
44 |
45 | If you would like to generate a new crate manually with `cargo new --lib `, make sure you include the following configuration in the generated `Cargo.toml`:
46 |
47 | ```toml
48 | [dependencies]
49 | near-sdk = "4.0.0"
50 |
51 | [lib]
52 | crate-type = ["cdylib"]
53 |
54 | [profile.release]
55 | codegen-units = 1
56 | # Tell `rustc` to optimize for small code size.
57 | opt-level = "z"
58 | lto = true
59 | debug = false
60 | panic = "abort"
61 | # Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
62 | overflow-checks = true
63 | ```
64 |
--------------------------------------------------------------------------------
/docs/testing/unit-tests.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Unit Tests
6 |
7 | Testing contract functionality can be done through the `cargo test` framework. These tests will run with a mocked blockchain and will allow testing function calls directly without having to set up/deploy to a network and sign serialized transactions on this network.
8 |
9 | A common framework for tests, along with setting up a basic testing environment looks like:
10 |
11 | ```rust
12 | #[cfg(all(test, not(target_arch = "wasm32")))]
13 | mod tests {
14 | use super::*;
15 | use near_sdk::test_utils::VMContextBuilder;
16 | use near_sdk::{testing_env, VMContext};
17 |
18 | fn get_context(is_view: bool) -> VMContext {
19 | VMContextBuilder::new()
20 | .signer_account_id("bob_near".parse().unwrap())
21 | .is_view(is_view)
22 | .build()
23 | }
24 |
25 | #[test]
26 | fn my_test() {
27 | let context = get_context(false);
28 | testing_env!(context);
29 | // ... Write test here
30 | }
31 | }
32 | ```
33 |
34 | Where `VMContextBuilder` allows for modifying the context of the mocked blockchain to simulate the environment that a transaction would be run. The documentation for what can be modified with this context can be found [here](https://docs.rs/near-sdk/latest/near_sdk/struct.VMContext.html).
35 |
36 | The `testing_env!` macro will initialize the blockchain interface with the `VMContext` which is either initialized through `VMContextBuilder` or manually through itself.
37 |
38 | > Note: This `testing_env!` and `VMContext` is only used for testing outside of `wasm` environments. When running the built contract on a network in a `wasm` environment, the context from the blockchain will be used through host functions on the runtime.
39 |
40 | To test read-only function calls, set `is_view` to `true` on the `VMContext`. This will test to verify that function calls which just read state do not try to modify state through unit tests. In the above example, `true` should be passed into the `get_context` call, which initializes the context as read-only.
41 |
42 | You will want to use `testing_env!` each time you need to update this context, such as mocking the `predecessor_accound_id` to simulate the functions being called by or only allowing view operations as mentioned above. Each time this is done, a new mocked blockchain will be initialized while keeping the existing state.
43 |
--------------------------------------------------------------------------------
/docs/contract-interface/private-methods.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | ---
4 |
5 | # Private Methods
6 |
7 | ## When using callbacks
8 |
9 | Usually, when a contract has to have a callback for a remote cross-contract call, this callback method should only be called by the contract itself. It's to avoid someone else calling it and messing the state. Pretty common pattern is to have an assertion that validates that the direct caller (predecessor account ID) matches to the contract's account (current account ID). Macro `#[private]` simplifies it, by making it a single line macro instead and improves readability.
10 |
11 | Use this annotation within the [`near_bindgen` macro](../contract-structure/near-bindgen.md) as follows:
12 |
13 | ```rust
14 | #[private]
15 | pub fn my_method(&mut self) {
16 | …
17 | }
18 | ```
19 |
20 | Which is equivalent to:
21 |
22 | ```rust
23 | pub fn my_method(&mut self ) {
24 | if near_sdk::env::current_account_id() != near_sdk::env::predecessor_account_id() {
25 | near_sdk::env::panic_str("Method method is private");
26 | }
27 | ...
28 | }
29 | ```
30 |
31 | Now with this annotation, only the account of the contract itself can call this method, either directly or through a promise.
32 |
33 | ## Writing internal methods
34 |
35 | Not all functions need to be exposed publicly. It may be beneficial to write private methods for helper or utility functions, for instance. There are three approaches to write internal methods:
36 |
37 | 1. Using `fn` instead of `pub fn`
38 |
39 | ```rust
40 | fn helper_method(a: u8, b: u8) {
41 | …
42 | }
43 | ```
44 |
45 | 2. Using `pub(crate) fn`. This may be helpful when an internal method is in a different module.
46 |
47 | ```rust
48 | // Function that can be called in another Rust file
49 | pub(crate) fn get_first_name(account: Account) {
50 | …
51 | }
52 | ```
53 |
54 | More information from the [official Rust docs](https://doc.rust-lang.org/reference/visibility-and-privacy.html) regarding public/private methods.
55 |
56 | 3. Separate `impl` block
57 |
58 | Another way of not exporting methods is by having a separate `impl Contract` section, that is not marked with `#[near_bindgen]`.
59 |
60 | ```rust
61 | #[near_bindgen]
62 | impl Contract {
63 | pub fn increment(&mut self) {
64 | self.internal_increment();
65 | }
66 | }
67 | impl Contract {
68 | /// This methods is still not exported.
69 | pub fn internal_increment(&mut self) {
70 | self.counter += 1;
71 | }
72 | }
73 | ```
74 |
--------------------------------------------------------------------------------
/docs/promises/create-account.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | ---
4 |
5 | # Creating Accounts
6 |
7 | You might want to create an account from a contract for many reasons. One example:
8 | You want to [progressively onboard](https://www.youtube.com/watch?v=7mO4yN1zjbs&t=2s) users, hiding the whole concept of NEAR from them at the beginning, and automatically create accounts for them (these could be sub-accounts of your main contract, such as `user123.some-cool-game.near`).
9 |
10 | Since an account with no balance is almost unusable, you probably want to combine this with the token transfer from [the last page](./token-tx.md). You will also need to give the account an access key. Here's a way do it:
11 |
12 | ```rust
13 | Promise::new("subaccount.example.near".parse().unwrap())
14 | .create_account()
15 | .add_full_access_key(env::signer_account_pk())
16 | .transfer(250_000_000_000_000_000_000_000); // 2.5e23yN, 0.25N
17 | ```
18 |
19 | In the context of a full contract:
20 |
21 | ```rust
22 | use near_sdk::{env, near_bindgen, AccountId, Balance, Promise};
23 |
24 | const INITIAL_BALANCE: Balance = 250_000_000_000_000_000_000_000; // 2.5e23yN, 0.25N
25 |
26 | #[near_bindgen]
27 | pub struct Contract {}
28 |
29 | #[near_bindgen]
30 | impl Contract {
31 | #[private]
32 | pub fn create_subaccount(prefix: AccountId) -> Promise {
33 | let subaccount_id = AccountId::new_unchecked(
34 | format!("{}.{}", prefix, env::current_account_id())
35 | );
36 | Promise::new(subaccount_id)
37 | .create_account()
38 | .add_full_access_key(env::signer_account_pk())
39 | .transfer(INITIAL_BALANCE)
40 | }
41 | }
42 | ```
43 |
44 | Things to note:
45 |
46 | * `add_full_access_key` – This example passes in the public key of the human or app that signed the original transaction that resulted in this function call ([`signer_account_pk`](https://docs.rs/near-sdk/3.1.0/near_sdk/env/fn.signer_account_id.html)). You could also use [`add_access_key`](https://docs.rs/near-sdk/latest/near_sdk/struct.Promise.html#method.add_access_key) to add a Function Call access key that only permits the account to make calls to a predefined set of contract functions.
47 | * `#[private]` – if you have a function that spends your contract's funds, you probably want to protect it in some way. This example does so with a perhaps-too-simple [`#[private]`](../contract-interface/private-methods.md) macro.
48 | * `INITIAL_BALANCE` uses the [`Balance`](https://docs.rs/near-sdk/3.1.0/near_sdk/type.Balance.html) type from near-sdk-rs. Today this is a simple alias for `u128`, but in the future may be expanded to have additional functionality, similar to recent [changes to the `Gas` type](https://github.com/near/near-sdk-rs/pull/471).
49 |
--------------------------------------------------------------------------------
/docs/contract-interface/public-methods.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Public Method Types
6 |
7 | Methods can be called externally by using the `pub` identifier within the [`#[near_bindgen]` macro](../contract-structure/near-bindgen.md) which will expose the method in the compiled WASM bytecode.
8 |
9 | It is important to only mark methods that should be called externally as public. If you need a contract to call itself, you can mark the function as public but add the [`#[private]` annotation](private-methods.md) so that it will panic if called from anything but the contract itself.
10 |
11 | A basic usage of this would look like the following:
12 |
13 | ```rust
14 | #[near_bindgen]
15 | impl MyContractStructure {
16 | pub fn some_method(&mut self) {
17 | // .. method logic here
18 | }
19 | }
20 | ```
21 |
22 | Where this would expose `some_method` from the WASM binary and allow it to be called externally.
23 |
24 |
25 | Expand to see generated code
26 |
27 | ```rust
28 | #[cfg(target_arch = "wasm32")]
29 | #[no_mangle]
30 | pub extern "C" fn some_method() {
31 | near_sdk::env::setup_panic_hook();
32 | if near_sdk::env::attached_deposit() != 0 {
33 | near_sdk::env::panic("Method some_method doesn\'t accept deposit".as_bytes());
34 | }
35 | let mut contract: MyContractStructure = near_sdk::env::state_read().unwrap_or_default();
36 | contract.some_method();
37 | near_sdk::env::state_write(&contract);
38 | }
39 | ```
40 |
41 |
42 | ## Exposing trait implementations
43 |
44 | Functions can also be exposed through trait implementations. This can be useful if implementing a shared interface or standard for a contract. This code generation is handled very similarly to basic `pub` functions, but the `#[near_bindgen]` macro only needs to be attached to the trait implementation, not the trait itself:
45 |
46 | ```rust
47 | pub trait MyTrait {
48 | fn trait_method(&mut self);
49 | }
50 |
51 | #[near_bindgen]
52 | impl MyTrait for MyContractStructure {
53 | fn trait_method(&mut self) {
54 | // .. method logic here
55 | }
56 | }
57 | ```
58 |
59 | In this example, the generated code will be the same as the previous example, except with a different method name.
60 |
61 |
62 | Expand to see generated code
63 |
64 | ```rust
65 | #[cfg(target_arch = "wasm32")]
66 | #[no_mangle]
67 | pub extern "C" fn trait_method() {
68 | near_sdk::env::setup_panic_hook();
69 | if near_sdk::env::attached_deposit() != 0 {
70 | near_sdk::env::panic("Method trait_method doesn\'t accept deposit".as_bytes());
71 | }
72 | let mut contract: MyContractStructure = near_sdk::env::state_read().unwrap_or_default();
73 | contract.trait_method();
74 | near_sdk::env::state_write(&contract);
75 | }
76 | ```
77 |
78 |
--------------------------------------------------------------------------------
/src/css/custom.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable docusaurus/copyright-header */
2 | /**
3 | * Any CSS included here will be global. The classic template
4 | * bundles Infima by default. Infima is a CSS framework designed to
5 | * work well for content-centric websites.
6 | */
7 |
8 | /* You can override the default Infima variables here. */
9 | :root {
10 | --near-color-black: #000000;
11 | --near-color-slate: #4b4f54;
12 | --near-color-red: #ff585d;
13 | --near-color-blue: #00C1DE;
14 | --near-color-royal-blue: #0072CE;
15 | --near-color-green: #00C08B;
16 | --near-color-yellow-light: #FAF8D1;
17 | --near-color-yellow: #F0EC74;
18 | --near-color-yellow-dark: #ECE750;
19 | --near-color-royal: #0072ce;
20 | --near-color-gray: #f2f2f2;
21 | --ifm-color-primary: #0072ce;
22 | /* TODO update theme variants */
23 | /* --ifm-color-primary-dark: rgb(33, 175, 144);
24 | --ifm-color-primary-darker: rgb(31, 165, 136);
25 | --ifm-color-primary-darkest: rgb(26, 136, 112);
26 | --ifm-color-primary-light: rgb(70, 203, 174);
27 | --ifm-color-primary-lighter: rgb(102, 212, 189);
28 | --ifm-color-primary-lightest: rgb(146, 224, 208); */
29 |
30 | --color-error: red;
31 | --color-footer: #23262a
32 |
33 | --ifm-code-font-size: 95%;
34 | }
35 |
36 | .docusaurus-highlight-code-line {
37 | background-color: rgb(72, 77, 91);
38 | display: block;
39 | margin: 0 calc(-1 * var(--ifm-pre-padding));
40 | padding: 0 var(--ifm-pre-padding);
41 | }
42 |
43 | a code {
44 | color: var(--near-color-blue);
45 | }
46 |
47 | .admonition-note {
48 | background-color: var(--near-color-gray);
49 | border-color: var(--near-color-gray);
50 | }
51 |
52 | .admonition-tip {
53 | background-color: var(--near-color-green);
54 | border-color: var(--near-color-green);
55 | }
56 |
57 | .admonition-info {
58 | background-color: var(--near-color-blue);
59 | border-color: var(--near-color-blue);
60 | }
61 |
62 | .admonition-caution {
63 | background-color: var(--near-color-yellow);
64 | border-color: var(--near-color-yellow);
65 | }
66 |
67 | .admonition-danger {
68 | background-color: var(--near-color-red);
69 | border-color: var(--near-color-red);
70 | }
71 |
72 | figure {
73 | margin: 0;
74 | }
75 |
76 | figcaption {
77 | background-color: #3F4246;
78 | color: #fff;
79 | font-size: smaller;
80 | padding: 3px;
81 | text-align: center;
82 | width: 600px;
83 | }
84 |
85 | figcaption.full-width {
86 | width: auto;
87 | }
88 |
89 | figcaption.small {
90 | width: 400px;
91 | }
92 |
93 | figcaption a, .alert figcaption a {
94 | color: #FFC860;
95 | }
96 |
97 | table {
98 | border-radius: 19px;
99 | border: 6px solid #A7A7A7;
100 | }
101 |
102 | table thead {
103 | background-color: rgba(171,208,85, .5);
104 | color: #fff;
105 | }
106 |
107 | table tr:nth-child(2n) {
108 | background-color: rgba(171,208,85, 0.19);
109 | }
110 |
--------------------------------------------------------------------------------
/docs/promises/token-tx.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # Sending $NEAR
6 |
7 | You might want to send tokens from a contract for many reasons.
8 |
9 | * The contract uses something like the [Storage Standard](https://nomicon.io/Standards/StorageManagement.html) and needs to return deposits to users when they unregister.
10 | * Users pay into the contract and the contract later pays these fees to the maintainers, redistributes them to users, or disburses them to some cause the users vote on.
11 | * And more!
12 |
13 | Blockchains give us programmable money, and the ability for a smart contract to send tokens lies at the heart of that ability.
14 |
15 | NEAR makes this easy. Transferring NEAR tokens is the simplest transaction you can send from a smart contract. Here's all you need:
16 |
17 | ```rust
18 | let amount: u128 = 1_000_000_000_000_000_000_000_000; // 1 $NEAR as yoctoNEAR
19 | let account_id: AccountId = "example.near".parse().unwrap();
20 |
21 | Promise::new(account_id).transfer(amount);
22 | ```
23 |
24 | In the context of a full contract and function call, this could look like:
25 |
26 | ```rust
27 | use near_sdk::{json_types::U128, near_bindgen, AccountId, Promise};
28 |
29 | #[near_bindgen]
30 | pub struct Contract {}
31 |
32 | #[near_bindgen]
33 | impl Contract {
34 | pub fn pay(amount: U128, to: AccountId) -> Promise {
35 | Promise::new(to).transfer(amount.0)
36 | }
37 | }
38 | ```
39 |
40 | Most of this is boilerplate you're probably familiar with by now – imports, setting up [`near_bindgen`](../contract-structure/near-bindgen.md), [borsh](../contract-interface/serialization-interface.md), etc. Some interesting details related to the transfer itself:
41 |
42 | * `U128` with a capital `U`: The `pay` method defined here accepts JSON as input, and numbers in JS [cannot be larger than `2^53-1`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER), so for compatibility with deserializing JSON to JS, the integer is serialized as a decimal string. Since the `transfer` method takes a number in [yocto](https://en.wikipedia.org/wiki/Yocto-)NEAR, it's likely to need numbers much larger than `2^53-1`.
43 |
44 | When a function takes `U128` as input, it means that callers need to specify the number a a string. near-sdk-rs will then cast it to `U128` type, which wraps Rust's native [`u128`](https://doc.rust-lang.org/std/primitive.u128.html). The underlying `u128` can be retrieved with `.0` – used in `transfer(amount.0)`.
45 |
46 | * `AccountId`: this will automatically check that the provided string is a well-formed NEAR account ID, and panic with a useful error if not.
47 |
48 | * Returning `Promise`: This allows NEAR Explorer, near-cli, near-api-js, and other tooling to correctly determine if a whole chain of transactions is successful. If your function does not return `Promise`, tools like near-cli will return immediately after your function call. And then even if the `transfer` fails, your function call will be considered successful. You can see a before & after example of this behavior [here](https://github.com/near-examples/rust-high-level-cross-contract/pull/73#issuecomment-902849410).
49 |
50 | Using near-cli, someone could invoke this function with a call like:
51 |
52 | near call $CONTRACT pay '{"amount": "1000000000000000000000000", "to": "example.near"}' --accountId benjiman.near
53 |
54 |
--------------------------------------------------------------------------------
/docs/promises/deploy-contract.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 4
3 | ---
4 |
5 | # Deploying Contracts
6 |
7 | You might want your smart contract to deploy subsequent smart contract code for a few reasons:
8 |
9 | * The contract acts as a Factory, a pattern where a parent contract creates many child contracts ([Mintbase](https://www.mintbase.io/) does this to create a new NFT store for [anyone who wants one](https://docs.mintbase.io/creating/store/deploy-fee); [Rainbow Bridge](https://near.org/bridge/) does this to deploy separate Fungible Token contracts for [each bridged token](https://github.com/aurora-is-near/rainbow-token-connector/blob/ce7640da144f000e0a93b6d9373bbc2514e37f3b/bridge-token-factory/src/lib.rs#L311-L341))
10 | * The contract updates its own code (calls `deploy` on itself) pending the outcome of [a DAO vote](../upgrading/via-dao-vote.md).
11 | * You could implement a "contract per user" system that creates app-specific subaccounts for users (`your-app.user1.near`, `your-app.user2.near`, etc) and deploys the same contract to each. This is currently prohibitively expensive due to NEAR's [storage fees](https://docs.near.org/concepts/storage/storage-staking), but that may be optimized in the future. If it is, this sort of "sharded app design" may become the more scalable, user-centric approach to contract standards and app mechanics. An early experiment with this paradigm was called [Meta NEAR](https://github.com/metanear).
12 |
13 | If your goal is to deploy to a subaccount of your main contract like Mintbase or the Rainbow Bridge, you will also need to create the account. So, combining concepts from the last few pages, here's what you need:
14 |
15 | ```rust
16 | const CODE: &[u8] = include_bytes!("./path/to/compiled.wasm");
17 |
18 | Promise::new("subaccount.example.near".parse().unwrap())
19 | .create_account()
20 | .add_full_access_key(env::signer_account_pk())
21 | .transfer(3_000_000_000_000_000_000_000_000) // 3e24yN, 3N
22 | .deploy_contract(CODE.to_vec())
23 | ```
24 |
25 | Here's what a full contract might look like, showing a naïve way to pass `code` as an argument rather than hard-coding it with `include_bytes!`:
26 |
27 | ```rust
28 | use near_sdk::{env, near_bindgen, AccountId, Balance, Promise};
29 |
30 | const INITIAL_BALANCE: Balance = 3_000_000_000_000_000_000_000_000; // 3e24yN, 3N
31 |
32 | #[near_bindgen]
33 | pub struct Contract {}
34 |
35 | #[near_bindgen]
36 | impl Contract {
37 | #[private]
38 | pub fn create_child_contract(prefix: AccountId, code: Vec) -> Promise {
39 | let subaccount_id = AccountId::new_unchecked(
40 | format!("{}.{}", prefix, env::current_account_id())
41 | );
42 | Promise::new(subaccount_id)
43 | .create_account()
44 | .add_full_access_key(env::signer_account_pk())
45 | .transfer(INITIAL_BALANCE)
46 | .deploy_contract(code)
47 | }
48 | }
49 | ```
50 |
51 | Why is this a naïve approach? It could run into issues because of the 4MB transaction size limit – the function above would deserialize and heap-allocate a whole contract. For many situations, the `include_bytes!` approach is preferable. If you really need to attach compiled Wasm as an argument, you might be able to copy the approach [used by Sputnik DAO v2](https://github.com/near-daos/sputnik-dao-contract/blob/a8fc9a8c1cbde37610e56e1efda8e5971e79b845/sputnikdao2/src/types.rs#L74-L142).
52 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BOUNTY.yml:
--------------------------------------------------------------------------------
1 | name: "Simple Bounty"
2 | description: "Use this template to create a HEROES Simple Bounty via Github bot"
3 | title: "Bounty: "
4 | labels: ["bounty"]
5 | assignees: heroes-bot-test
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | Hi! Let's set up your bounty! Please don't change the template - @heroes-bot-test won't be able to help you.
11 |
12 | - type: dropdown
13 | id: type
14 | attributes:
15 | label: What talent are you looking for?
16 | options:
17 | - Marketing
18 | - Development
19 | - Design
20 | - Other
21 | - Content
22 | - Research
23 | - Audit
24 |
25 | - type: textarea
26 | id: description
27 | attributes:
28 | label: What you need to be done?
29 |
30 | - type: dropdown
31 | id: tags
32 | attributes:
33 | label: Tags
34 | description: Add tags that match the topic of the work
35 | multiple: true
36 | options:
37 | - API
38 | - Blockchain
39 | - Community
40 | - CSS
41 | - DAO
42 | - dApp
43 | - DeFi
44 | - Design
45 | - Documentation
46 | - HTML
47 | - Javascript
48 | - NFT
49 | - React
50 | - Rust
51 | - Smart contract
52 | - Typescript
53 | - UI/UX
54 | - web3
55 | - Translation
56 | - Illustration
57 | - Branding
58 | - Copywriting
59 | - Blogging
60 | - Editing
61 | - Video Creation
62 | - Social Media
63 | - Graphic Design
64 | - Transcription
65 | - Product Design
66 | - Artificial Intelligence
67 | - Quality Assurance
68 | - Risk Assessment
69 | - Security Audit
70 | - Bug Bounty
71 | - Code Review
72 | - Blockchain Security
73 | - Smart Contract Testing
74 | - Penetration Testing
75 | - Vulnerability Assessment
76 | - BOS
77 | - News
78 | - Hackathon
79 | - NEARCON2023
80 | - NEARWEEK
81 |
82 | - type: input
83 | id: deadline
84 | attributes:
85 | label: Deadline
86 | description: "Set a deadline for your bounty. Please enter the date in format: DD.MM.YYYY"
87 | placeholder: "19.05.2027"
88 |
89 | - type: dropdown
90 | id: currencyType
91 | attributes:
92 | label: Currency
93 | description: What is the currency you want to pay?
94 | options:
95 | - USDC.e
96 | - USDT.e
97 | - DAI
98 | - wNEAR
99 | - USDt
100 | - XP
101 | - marmaj
102 | - NEKO
103 | - JUMP
104 | - USDC
105 | - NEARVIDIA
106 | default: 0
107 | validations:
108 | required: true
109 |
110 | - type: input
111 | id: currencyAmount
112 | attributes:
113 | label: Amount
114 | description: How much it will be cost?
115 |
116 | - type: markdown
117 | attributes:
118 | value: "## Advanced settings"
119 |
120 | - type: checkboxes
121 | id: kyc
122 | attributes:
123 | label: KYC
124 | description: "Use HEROES' KYC Verification, only applicants who passed HEROES' KYC can apply and work on this bounty!"
125 | options:
126 | - label: Use KYC Verification
127 |
128 | - type: markdown
129 | attributes:
130 | value: |
131 | ### This cannot be changed once the bounty is live!
132 |
--------------------------------------------------------------------------------
/docusaurus.config.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | // Note: type annotations allow type checking and IDEs autocompletion
3 |
4 | /** @type {import('@docusaurus/types').Config} */
5 | const config = {
6 | title: 'NEAR SDK docs',
7 | tagline: 'Write smart contracts to run on the NEAR blockchain!',
8 | url: 'https://near-sdk.io/',
9 | baseUrl: '/',
10 | onBrokenLinks: 'throw',
11 | onBrokenMarkdownLinks: 'warn',
12 | favicon: 'img/favicon.ico',
13 | organizationName: 'NEAR',
14 | projectName: 'sdk-docs',
15 | themes: [
16 | '@saucelabs/theme-github-codeblock'
17 | ],
18 | themeConfig: ({
19 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
20 | algolia: {
21 | appId: "0LUM67N2P2",
22 | apiKey: "129d0f429e1bb0510f0261dda1e88ed4",
23 | indexName: "near",
24 | contextualSearch: true,
25 | externalUrlRegex: "near\\.org|near-sdk\\.io",
26 | // Optional: Algolia search parameters
27 | searchParameters: {},
28 | },
29 | prism: {
30 | additionalLanguages: ['rust'],
31 | },
32 | colorMode: {
33 | defaultMode: 'dark',
34 | },
35 | navbar: {
36 | title: '',
37 | logo: {
38 | alt: 'NEAR logo',
39 | src: 'img/near_logo.svg',
40 | srcDark: 'img/near_logo_white.svg',
41 | },
42 | items: [
43 | {
44 | href: 'https://docs.rs/near-sdk/',
45 | label: 'docs.rs',
46 | position: 'right',
47 | },
48 | {
49 | href: 'https://github.com/near/near-sdk-rs',
50 | label: 'Rust SDK GitHub',
51 | position: 'right',
52 | },
53 | {
54 | href: 'https://github.com/near/sdk-docs',
55 | label: 'Docs GitHub',
56 | position: 'right',
57 | },
58 | ],
59 | },
60 | footer: {
61 | style: 'dark',
62 | links: [
63 | {
64 | title: 'Community',
65 | items: [
66 | {
67 | label: 'Stack Overflow',
68 | href: 'https://stackoverflow.com/questions/tagged/nearprotocol',
69 | },
70 | {
71 | label: 'Discord',
72 | href: 'https://discord.com/invite/UY9Xf2k',
73 | },
74 | {
75 | label: 'Twitter',
76 | href: 'https://twitter.com/NEARProtocol',
77 | },
78 | ],
79 | },
80 | {
81 | title: 'More',
82 | items: [
83 | {
84 | label: 'SDK GitHub',
85 | href: 'https://github.com/near/sdk-docs',
86 | },
87 | ],
88 | },
89 | ],
90 | copyright: `${new Date().getFullYear()} NEAR Protocol | All rights reserved | hello@near.org`,
91 | },
92 | }),
93 | presets: [
94 | [
95 | 'classic',
96 | /** @type {import('@docusaurus/preset-classic').Options} */
97 | ({
98 | docs: {
99 | sidebarPath: require.resolve('./sidebars.js'),
100 | editUrl:
101 | 'https://github.com/near/sdk-docs/edit/main/',
102 | routeBasePath: '/',
103 | "showLastUpdateAuthor": true,
104 | "showLastUpdateTime": true,
105 | "path": "./docs",
106 | },
107 | theme: {
108 | customCss: require.resolve('./src/css/custom.css'),
109 | },
110 | sitemap: {
111 | changefreq: 'weekly',
112 | priority: 0.5,
113 | },
114 | gtag: {
115 | trackingID: 'G-NEHEBVDQKL',
116 | anonymizeIP: true,
117 | },
118 | }),
119 | ],
120 | ],
121 | };
122 |
123 | module.exports = config;
124 |
--------------------------------------------------------------------------------
/docs/contract-structure/near-bindgen.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # near_bindgen
6 |
7 | The `#[near_bindgen]` macro is used on a `struct` and the function implementations to generate the necessary code to be a valid NEAR contract and expose the intended functions to be able to be called externally.
8 |
9 | For example, on a simple counter contract, the macro will be applied as such:
10 |
11 | ```rust
12 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
13 | use near_sdk::near_bindgen;
14 |
15 | #[near_bindgen]
16 | #[derive(BorshDeserialize, BorshSerialize, Default)]
17 | pub struct Counter {
18 | value: u64,
19 | }
20 |
21 | #[near_bindgen]
22 | impl Counter {
23 | pub fn increment(&mut self) {
24 | self.value += 1;
25 | }
26 |
27 | pub fn get_count(&self) -> u64 {
28 | self.value
29 | }
30 | }
31 | ```
32 |
33 | In this example, the `Counter` struct represents the smart contract state and anything that implements `BorshSerialize` and `BorshDeserialize` can be included, even `collections`, which will be covered in the next section. Whenever a function is called, the state will be loaded and deserialized, so it's important to keep this amount of data loaded as minimal as possible.
34 |
35 | `#[near_bindgen]` also annotates the `impl` for `Counter` and this will generate any necessary boilerplate to expose the functions. The core interactions that are important to keep in mind:
36 | - Any `pub` functions will be callable externally from any account/contract.
37 | - For more information, see [public methods](../contract-interface/public-methods.md)
38 | - `self` can be used in multiple ways to control the [mutability of the contract](../contract-interface/contract-mutability.md):
39 | - Functions that take `&self` or `self` will be read-only and do not write the updated state to storage
40 | - Functions that take `&mut self` allow for mutating state, and state will always be written back at the end of the function call
41 | - Exposed functions can omit reading and writing to state if `self` is not included in the function params
42 | - This can be useful for some static functionality or returning data embedded in the contract code
43 | - If the function has a return value, it will be serialized and attached as a result through `env::value_return`
44 |
45 |
46 |
47 | ## Initialization Methods
48 |
49 | By default, the `Default::default()` implementation of a contract will be used to initialize a contract. There can be a custom initialization function which takes parameters or performs custom logic with the following `#[init]` annotation:
50 |
51 | ```rust
52 | #[near_bindgen]
53 | impl Counter {
54 | #[init]
55 | pub fn new(value: u64) -> Self {
56 | log!("Custom counter initialization!");
57 | Self { value }
58 | }
59 | }
60 | ```
61 |
62 | All contracts are expected to implement `Default`. If you would like to prohibit the default implementation from being used, the `PanicOnDefault` derive macro can be used:
63 |
64 | ```rust
65 | #[near_bindgen]
66 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
67 | pub struct Counter {
68 | // ...
69 | }
70 | ```
71 |
72 | ## Payable Methods
73 |
74 | Methods can be annotated with `#[payable]` to allow tokens to be transferred with the method invocation. For more information, see [payable methods](../contract-interface/payable-methods.md).
75 |
76 | To declare a function as payable, use the `#[payable]` annotation as follows:
77 |
78 | ```rust
79 | #[payable]
80 | pub fn my_method(&mut self) {
81 | ...
82 | }
83 | ```
84 |
85 | ## Private Methods
86 |
87 | Some methods need to be exposed to allow the contract to call a method on itself through a promise, but want to disallow any other contract to call it. For this, use the `#[private]` annotation to panic when this method is called externally. See [private methods](../contract-interface/private-methods.md) for more information.
88 |
89 | This annotation can be applied to any method through the following:
90 |
91 | ```rust
92 | #[private]
93 | pub fn my_method(&mut self) {
94 | ...
95 | }
96 | ```
97 |
--------------------------------------------------------------------------------
/src/theme/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 |
4 | import Link from '@docusaurus/Link';
5 | import {FooterLinkItem, useThemeConfig} from '@docusaurus/theme-common';
6 | import useBaseUrl from '@docusaurus/useBaseUrl';
7 | import styles from './styles.module.css';
8 | import ThemedImage, {Props as ThemedImageProps} from '@theme/ThemedImage';
9 |
10 | function FooterLink({
11 | to,
12 | href,
13 | label,
14 | prependBaseUrlToHref,
15 | ...props
16 | }: FooterLinkItem) {
17 | const toUrl = useBaseUrl(to);
18 | const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
19 |
20 | return (
21 |
31 | {label}
32 |
33 | );
34 | }
35 |
36 | const FooterLogo = ({
37 | sources,
38 | alt,
39 | }: Pick) => (
40 |
41 | );
42 |
43 | function Footer(): JSX.Element | null {
44 | const {footer} = useThemeConfig();
45 |
46 | const {copyright, links = [], logo = {}} = footer || {};
47 | const sources = {
48 | light: useBaseUrl(logo.src),
49 | dark: useBaseUrl(logo.srcDark || logo.src),
50 | };
51 |
52 | if (!footer) {
53 | return null;
54 | }
55 |
56 | return (
57 |
123 | );
124 | }
125 |
126 | export default Footer;
127 |
--------------------------------------------------------------------------------
/docs/best-practices.md:
--------------------------------------------------------------------------------
1 | ---
2 | slug: /best-practices
3 | ---
4 |
5 | # Best practices
6 |
7 | ## Enable overflow checks
8 |
9 | It's usually helpful to panic on integer overflow. To enable it, add the following into your `Cargo.toml` file:
10 |
11 | ```toml
12 | [profile.release]
13 | overflow-checks = true
14 | ```
15 |
16 | ## Use `require!` early
17 |
18 | Try to validate the input, context, state and access using `require!` before taking any actions. The earlier you panic, the more [gas](https://docs.near.org/concepts/basics/transactions/gas) you will save for the caller.
19 |
20 | ```rust
21 | #[near_bindgen]
22 | impl Contract {
23 | pub fn set_fee(&mut self, new_fee: Fee) {
24 | require!(env::predecessor_account_id() == self.owner_id, "Owner's method");
25 | new_fee.assert_valid();
26 | self.internal_set_fee(new_fee);
27 | }
28 | }
29 | ```
30 |
31 | **Note**: If you want debug information in the panic message or if you are using an SDK version before `4.0.0-pre.2`,
32 | the Rust `assert!` macro can be used instead of `require!`.
33 |
34 | ```rust
35 | #[near_bindgen]
36 | impl Contract {
37 | pub fn set_fee(&mut self, new_fee: Fee) {
38 | assert_eq!(env::predecessor_account_id(), self.owner_id, "Owner's method");
39 | new_fee.assert_valid();
40 | self.internal_set_fee(new_fee);
41 | }
42 | }
43 | ```
44 |
45 | ## Use `log!`
46 |
47 | Use logging for debugging and notifying user.
48 |
49 | When you need a formatted message, you can use the following macro:
50 |
51 | ```rust
52 | log!("Transferred {} tokens from {} to {}", amount, sender_id, receiver_id);
53 | ```
54 |
55 | It's equivalent to the following message:
56 |
57 | ```rust
58 | env::log_str(format!("Transferred {} tokens from {} to {}", amount, sender_id, receiver_id).as_ref());
59 | ```
60 |
61 | ## Return `Promise`
62 |
63 | If your method makes a cross-contract call, you probably want to return the newly created `Promise`.
64 | This allows the caller (such as a near-cli or near-api-js call) to wait for the result of the promise instead of returning immediately.
65 | Additionally, if the promise fails for some reason, returning it will let the caller know about the failure, as well as enabling NEAR Explorer and other tools to mark the whole transaction chain as failing.
66 | This can prevent false-positives when the first or first few transactions in a chain succeed but a subsequent transaction fails.
67 |
68 | E.g.
69 |
70 | ```rust
71 | #[near_bindgen]
72 | impl Contract {
73 | pub fn withdraw_100(&mut self, receiver_id: AccountId) -> Promise {
74 | Promise::new(receiver_id).transfer(100)
75 | }
76 | }
77 | ```
78 |
79 | ## Reuse crates from `near-sdk`
80 |
81 | `near-sdk` re-exports the following crates:
82 |
83 | - `borsh`
84 | - `base64`
85 | - `bs58`
86 | - `serde`
87 | - `serde_json`
88 |
89 | Most common crates include `borsh` which is needed for internal STATE serialization and
90 | `serde` for external JSON serialization.
91 |
92 | When marking structs with `serde::Serialize` you need to use `#[serde(crate = "near_sdk::serde")]`
93 | to point serde to the correct base crate.
94 |
95 | ```rust
96 | /// Import `borsh` from `near_sdk` crate
97 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
98 | /// Import `serde` from `near_sdk` crate
99 | use near_sdk::serde::{Serialize, Deserialize};
100 |
101 | /// Main contract structure serialized with Borsh
102 | #[near_bindgen]
103 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
104 | pub struct Contract {
105 | pub pair: Pair,
106 | }
107 |
108 | /// Implements both `serde` and `borsh` serialization.
109 | /// `serde` is typically useful when returning a struct in JSON format for a frontend.
110 | #[derive(Serialize, Deserialize, BorshDeserialize, BorshSerialize)]
111 | #[serde(crate = "near_sdk::serde")]
112 | pub struct Pair {
113 | pub a: u32,
114 | pub b: u32,
115 | }
116 |
117 | #[near_bindgen]
118 | impl Contract {
119 | #[init]
120 | pub fn new(pair: Pair) -> Self {
121 | Self {
122 | pair,
123 | }
124 | }
125 |
126 | pub fn get_pair(self) -> Pair {
127 | self.pair
128 | }
129 | }
130 | ```
131 |
132 | ## `std::panic!` vs `env::panic`
133 |
134 | - `std::panic!` panics the current thread. It uses `format!` internally, so it can take arguments.
135 | SDK sets up a panic hook, which converts the generated `PanicInfo` from `panic!` into a string and uses `env::panic` internally to report it to Runtime.
136 | This may provide extra debugging information such as the line number of the source code where the panic happened.
137 |
138 | - `env::panic` directly calls the host method to panic the contract.
139 | It doesn't provide any other extra debugging information except for the passed message.
140 |
141 | ## Use workspaces
142 |
143 | Workspaces allow you to automate workflows and run tests for multiple contracts and cross-contract calls in a sandbox or testnet environment.
144 | Read more, [workspaces-rs](https://github.com/near/workspaces-rs) or [workspaces-js](https://github.com/near/workspaces-js).
145 |
--------------------------------------------------------------------------------
/docs/contract-interface/contract-mutability.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # Contract Mutability
6 |
7 | Contract state mutability is handled automatically based on how [`self`](https://doc.rust-lang.org/std/keyword.self.html) is used in the function parameters. Depending on which is used, the [`#[near_bindgen]`](../contract-structure/near-bindgen.md) macro will generate the respective code to load/deserialize state for any function which uses `self` and serialize/store state only for when `&mut self` is used.
8 |
9 | The following semantics are consistent for all [public methods](public-methods.md).
10 |
11 | ## Read-Only Functions
12 |
13 | To access state immutably, where the existing state is not overwritten at the end of the transaction, you can use `&self` or `self` as a parameter. Both of these will generate the same code to load and deserialize the state into the structure and call the function, but the difference is that `&self` will just pass a reference to this variable into the function where `self` will move the variable into the function.
14 |
15 | For more information about `&self` versus `self` see [this section in the Rust book](https://doc.rust-lang.org/stable/book/ch05-03-method-syntax.html?highlight=capture%20self#defining-methods).
16 |
17 | Here are some examples of using each:
18 |
19 | ```rust
20 | #[near_bindgen]
21 | #[derive(BorshDeserialize, BorshSerialize, Default)]
22 | pub struct MyContractStructure {
23 | integer: u64,
24 | message: String,
25 | }
26 | #[near_bindgen]
27 | impl MyContractStructure {
28 | pub fn get_values(self) -> (u64, String) {
29 | (self.integer, self.message)
30 | }
31 | pub fn log_state_string(&self) {
32 | near_sdk::env::log(self.message.as_bytes());
33 | }
34 | }
35 | ```
36 |
37 | There is no simple guideline that works for every case, but here are some core reasons on when to use each:
38 |
39 | ### self (owned value)
40 |
41 | Moving the owned value into the function can be useful if `self` itself or its fields are moved within the function, as it will remove the need to `Clone`/`Copy` the data.
42 |
43 | Example:
44 |
45 | ```rust
46 | /// View method. More efficient, but can't be reused internally, because it consumes self.
47 | pub fn get_owner_id(self) -> AccountId {
48 | self.owner_id
49 | }
50 | ```
51 |
52 | ### &self (immutable reference)
53 |
54 | This should be used when the contract state is only read or the function is re-used by other methods which do not have [ownership](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html) of the variable. This can also be useful if the struct uses a lot of memory, to avoid moving a large amount of data into the function scope rather than just referencing it.
55 |
56 | Example:
57 |
58 | ```rust
59 | /// View method. Requires cloning the account id.
60 | pub fn get_owner_id(&self) -> AccountId {
61 | self.owner_id.clone()
62 | }
63 | ```
64 |
65 | ### Returning derived data
66 |
67 | Some less common cases may intend to use read-only methods to return objects that are derived from modified objects stored in state. Below is a demonstration of this concept:
68 |
69 | ```rust
70 | /// View method that "modifies" state, for code structure or computational
71 | /// efficiency reasons. Changes state in-memory, but does NOT save the new
72 | /// state. If called internally by a change method, WILL result in updated
73 | /// contract state.
74 | pub fn update_stats(&self, account_id: AccountId, score: U64) -> Account {
75 | let account = self.accounts.get(&account_id).unwrap_or_else(|| env::panic_str("ERR_ACCT_NOT_FOUND"));
76 | account.total += score;
77 | account
78 | }
79 | ```
80 |
81 | ## Mutable Functions
82 |
83 | Mutable functions allow for loading the existing state, modifying it, then rewriting the modified state at the end of the function call. This should be used for any transaction which modifies the contract state. Note that the serialized contract data is stored in persistent storage under the key `STATE`.
84 |
85 | An example of a mutable function is as follows:
86 |
87 | ```rust
88 | #[near_bindgen]
89 | #[derive(BorshDeserialize, BorshSerialize, Default)]
90 | pub struct MyContractStructure {
91 | integer: u64,
92 | }
93 | #[near_bindgen]
94 | impl MyContractStructure {
95 | pub fn modify_value(&mut self, new_value: u64) {
96 | self.integer = new_value;
97 | }
98 | pub fn increment_value(&mut self) {
99 | self.integer += 1;
100 | }
101 | }
102 | ```
103 |
104 | ## Pure Functions
105 |
106 | These functions do not use `self` at all, and will not read or write the contract state from storage. Using public pure functions will be very rare but can be useful if returning data embedded in the contract code or executing some static shared logic that doesn't depend on state.
107 |
108 | Some examples of pure functions are as follows:
109 |
110 | ```rust
111 | const SOME_VALUE: u64 = 8;
112 |
113 | #[near_bindgen]
114 | impl MyContractStructure {
115 | pub fn log_message(/* Parameters here */) {
116 | near_sdk::log!("inside log message");
117 | }
118 | pub fn log_u64(value: u64) {
119 | near_sdk::log!("{}", value);
120 | }
121 | pub fn return_static_u64() -> u64 {
122 | SOME_VALUE
123 | }
124 | }
125 | ```
126 |
--------------------------------------------------------------------------------
/docs/reducing-contract-size/examples.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Advice & examples
6 |
7 | This page is made for developers familiar with lower-level concepts who wish to reduce their contract size significantly, perhaps at the expense of code readability.
8 |
9 | Some common scenarios where this approach may be helpful:
10 |
11 | - contracts intended to be tied to one's account management
12 | - contracts deployed using a factory
13 | - future advancements similar to the EVM on NEAR
14 |
15 | There have been a few items that may add unwanted bytes to a contract's size when compiled. Some of these may be more easily swapped for other approaches while others require more internal knowledge about system calls.
16 |
17 | ## Small wins
18 |
19 | ### Using flags
20 |
21 | When compiling a contract make sure to pass flag `-C link-arg=-s` to the rust compiler:
22 |
23 | ```bash
24 | RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release
25 | ```
26 |
27 | Here is the parameters we use for the most examples in `Cargo.toml`:
28 |
29 | ```toml
30 | [profile.release]
31 | codegen-units = 1
32 | opt-level = "s"
33 | lto = true
34 | debug = false
35 | panic = "abort"
36 | overflow-checks = true
37 | ```
38 |
39 | You may want to experiment with using `opt-level = "z"` instead of `opt-level = "s"` to see if generates a smaller binary. See more details on this in [The Cargo Book Profiles section](https://doc.rust-lang.org/cargo/reference/profiles.html#opt-level). You may also reference this [Shrinking .wasm Size](https://rustwasm.github.io/book/reference/code-size.html#tell-llvm-to-optimize-for-size-instead-of-speed) resource.
40 |
41 | ### Removing `rlib` from the manifest
42 |
43 | Ensure that your manifest (`Cargo.toml`) doesn't contain `rlib` unless it needs to. Some NEAR examples have included this:
44 |
45 | :::caution Adds unnecessary bloat
46 |
47 | ```toml
48 | [lib]
49 | crate-type = ["cdylib", "rlib"]
50 | ```
51 | :::
52 |
53 | when it could be:
54 |
55 | :::tip
56 |
57 | ```toml
58 | [lib]
59 | crate-type = ["cdylib"]
60 | ```
61 | :::
62 |
63 | 3. When using the Rust SDK, you may override the default JSON serialization to use [Borsh](https://borsh.io) instead. [See this page](/contract-interface/serialization-interface#overriding-serialization-protocol-default) for more information and an example.
64 | 4. When using assertions or guards, avoid using the standard `assert` macros like [`assert!`](https://doc.rust-lang.org/std/macro.assert.html), [`assert_eq!`](https://doc.rust-lang.org/std/macro.assert_eq.html), or [`assert_ne!`](https://doc.rust-lang.org/std/macro.assert_ne.html) as these may add bloat for information regarding the line number of the error. There are similar issues with `unwrap`, `expect`, and Rust's `panic!()` macro.
65 |
66 | Example of a standard assertion:
67 |
68 | :::caution Adds unnecessary bloat
69 |
70 | ```rust
71 | assert_eq!(contract_owner, predecessor_account, "ERR_NOT_OWNER");
72 | ```
73 | :::
74 |
75 | when it could be:
76 |
77 | :::tip
78 |
79 | ```rust
80 | if contract_owner != predecessor_account {
81 | env::panic(b"ERR_NOT_OWNER");
82 | }
83 | ```
84 | :::
85 |
86 | Example of removing `expect`:
87 |
88 | :::caution Adds unnecessary bloat
89 |
90 | ```rust
91 | let owner_id = self.owner_by_id.get(&token_id).expect("Token not found");
92 | ```
93 | :::
94 |
95 | when it could be:
96 |
97 | :::tip
98 |
99 | ```rust
100 | fn expect_token_found(option: Option) -> T {
101 | option.unwrap_or_else(|| env::panic_str("Token not found"))
102 | }
103 | let owner_id = expect_token_found(self.owner_by_id.get(&token_id));
104 | ```
105 | :::
106 |
107 | Example of changing standard `panic!()`:
108 |
109 | :::caution Adds unnecessary bloat
110 |
111 | ```rust
112 | panic!("ERR_MSG_HERE");
113 | ```
114 | :::
115 |
116 | when it could be:
117 |
118 | :::tip
119 |
120 | ```rust
121 | env::panic_str("ERR_MSG_HERE");
122 | ```
123 | :::
124 |
125 | ## Lower-level approach
126 |
127 | For a `no_std` approach to minimal contracts, observe the following examples:
128 |
129 | - [Tiny contract](https://github.com/near/nearcore/tree/1e7c6613f65c23f87adf2c92e3d877f4ffe666ea/runtime/near-test-contracts/tiny-contract-rs)
130 | - [NEAR ETH Gateway](https://github.com/ilblackdragon/near-eth-gateway/blob/master/proxy/src/lib.rs)
131 | - [This YouTube video](https://youtu.be/Hy4VBSCqnsE) where Eugene demonstrates a fungible token in `no_std` mode. The code for this [example lives here](https://github.com/near/core-contracts/pull/88).
132 | - [Examples using a project called `nesdie`](https://github.com/austinabell/nesdie/tree/main/examples).
133 | - Note that Aurora has found success using [rjson](https://crates.io/crates/rjson) as a lightweight JSON serialization crate. It has a smaller footprint than [serde](https://crates.io/crates/serde) which is currently packaged with the Rust SDK. See [this example of rjson](https://github.com/aurora-is-near/aurora-engine/blob/65a1d11fcd16192cc1bda886c62005c603189a24/src/json.rs#L254) in an Aurora repository, although implementation details will have to be gleaned by the reader and won't be expanded upon here. [This nesdie example](https://github.com/austinabell/nesdie/blob/bb6beb77e32cd54077ac54bf028f262a9dfb6ad0/examples/multisig/src/utils/json/vector.rs#L26-L30) also uses the [miniserde crate](https://crates.io/crates/miniserde), which is another option to consider for folks who choose to avoid using the Rust SDK.
134 |
135 | :::note Information on system calls
136 |
137 | Expand to see what's available from sys.rs
138 |
139 | ```rust reference
140 | https://github.com/near/near-sdk-rs/blob/master/near-sdk/src/environment/sys.rs
141 | ```
142 |
143 | :::
144 |
--------------------------------------------------------------------------------
/docs/contract-structure/nesting.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | ---
4 |
5 | # Collections Nesting
6 |
7 | ## Traditional approach for unique prefixes
8 |
9 | Hardcoded prefixes in the constructor using a short one letter prefix that was converted to a vector of bytes.
10 | When using nested collection, the prefix must be constructed manually.
11 |
12 | ```rust
13 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
14 | use near_sdk::collections::{UnorderedMap, UnorderedSet};
15 | use near_sdk::{near_bindgen, AccountId};
16 |
17 | #[near_bindgen]
18 | #[derive(BorshDeserialize, BorshSerialize)]
19 | pub struct Contract {
20 | pub accounts: UnorderedMap>,
21 | }
22 |
23 | impl Default for Contract {
24 | fn default() -> Self {
25 | Self {
26 | accounts: UnorderedMap::new(b"t"),
27 | }
28 | }
29 | }
30 |
31 | #[near_bindgen]
32 | impl Contract {
33 | pub fn get_tokens(&self, account_id: &AccountId) -> UnorderedSet {
34 | let tokens = self.accounts.get(account_id).unwrap_or_else(|| {
35 | // Constructing a unique prefix for a nested UnorderedSet from a concatenation
36 | // of a prefix and a hash of the account id.
37 | let prefix: Vec = [
38 | b"s".as_slice(),
39 | &near_sdk::env::sha256_array(account_id.as_bytes()),
40 | ]
41 | .concat();
42 | UnorderedSet::new(prefix)
43 | });
44 | tokens
45 | }
46 | }
47 | ```
48 |
49 | ## Generating unique prefixes for persistent collections
50 |
51 | Read more about persistent collections [from this documentation](/contract-structure/collections) or from [the Rust docs](https://docs.rs/near-sdk/latest/near_sdk/collections).
52 |
53 | Every instance of a persistent collection requires a unique storage prefix.
54 | The prefix is used to generate internal keys to store data in persistent storage.
55 | These internal keys need to be unique to avoid collisions (including collisions with key `STATE`).
56 |
57 | When a contract gets complicated, there may be multiple different
58 | collections that are not all part of the main structure, but instead part of a sub-structure or nested collections.
59 | They all need to have unique prefixes.
60 |
61 | We can introduce an `enum` for tracking storage prefixes and keys.
62 | And then use borsh serialization to construct a unique prefix for every collection.
63 | It's as efficient as manually constructing them, because with Borsh serialization, an enum only takes one byte.
64 |
65 | ```rust
66 | use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
67 | use near_sdk::collections::{UnorderedMap, UnorderedSet};
68 | use near_sdk::{env, near_bindgen, AccountId, BorshStorageKey, CryptoHash};
69 |
70 | #[near_bindgen]
71 | #[derive(BorshDeserialize, BorshSerialize)]
72 | pub struct Contract {
73 | pub accounts: UnorderedMap>,
74 | }
75 |
76 | impl Default for Contract {
77 | fn default() -> Self {
78 | Self {
79 | accounts: UnorderedMap::new(StorageKeys::Accounts),
80 | }
81 | }
82 | }
83 |
84 | #[derive(BorshStorageKey, BorshSerialize)]
85 | pub enum StorageKeys {
86 | Accounts,
87 | SubAccount { account_hash: CryptoHash },
88 | }
89 |
90 | #[near_bindgen]
91 | impl Contract {
92 | pub fn get_tokens(&self, account_id: &AccountId) -> UnorderedSet {
93 | let tokens = self.accounts.get(account_id).unwrap_or_else(|| {
94 | UnorderedSet::new(StorageKeys::SubAccount {
95 | account_hash: env::sha256_array(account_id.as_bytes()),
96 | })
97 | });
98 | tokens
99 | }
100 | }
101 | ```
102 |
103 | ## Error prone patterns
104 |
105 | By extension of the error-prone patterns to avoid mentioned in the [collections section](./collections.md#error-prone-patterns), it is important to keep in mind how these bugs can easily be introduced into a contract when using nested collections.
106 |
107 | Some issues for more context:
108 | - https://github.com/near/near-sdk-rs/issues/560
109 | - https://github.com/near/near-sdk-rs/issues/703
110 |
111 | The following cases are the most commonly encountered bugs that cannot be restricted at the type level:
112 |
113 | ```rust
114 | use near_sdk::borsh::{self, BorshSerialize};
115 | use near_sdk::collections::{LookupMap, UnorderedSet};
116 | use near_sdk::BorshStorageKey;
117 |
118 | #[derive(BorshStorageKey, BorshSerialize)]
119 | pub enum StorageKey {
120 | Root,
121 | Nested(u8),
122 | }
123 |
124 | // Bug 1: Nested collection is removed without clearing it's own state.
125 | let mut root: LookupMap> = LookupMap::new(StorageKey::Root);
126 | let mut nested = UnorderedSet::new(StorageKey::Nested(1));
127 | nested.insert(&"test".to_string());
128 | root.insert(&1, &nested);
129 |
130 | // Remove inserted collection without clearing it's sub-state.
131 | let mut _removed = root.remove(&1).unwrap();
132 |
133 | // This line would fix the bug:
134 | // _removed.clear();
135 |
136 | // This collection will now be in an inconsistent state if an empty UnorderedSet is put
137 | // in the same entry of `root`.
138 | root.insert(&1, &UnorderedSet::new(StorageKey::Nested(1)));
139 | let n = root.get(&1).unwrap();
140 | assert!(n.is_empty());
141 | assert!(n.contains(&"test".to_string()));
142 |
143 | // Bug 2 (only relevant for `near_sdk::collections`, not `near_sdk::store`): Nested
144 | // collection is modified without updating the collection itself in the outer collection.
145 | //
146 | // This is fixed at the type level in `near_sdk::store` because the values are modified
147 | // in-place and guarded by regular Rust borrow-checker rules.
148 | root.insert(&2, &UnorderedSet::new(StorageKey::Nested(2)));
149 |
150 | let mut nested = root.get(&2).unwrap();
151 | nested.insert(&"some value".to_string());
152 |
153 | // This line would fix the bug:
154 | // root.insert(&2, &nested);
155 |
156 | let n = root.get(&2).unwrap();
157 | assert!(n.is_empty());
158 | assert!(n.contains(&"some value".to_string()));
159 | ```
160 |
--------------------------------------------------------------------------------
/docs/upgrading/prototyping.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | sidebar_label: Rapid Prototyping
4 | title: "Upgrading Contracts: Rapid Prototyping"
5 | ---
6 |
7 | # Upgrading Contracts
8 |
9 | When you change the interface of a contract and re-deploy it, you may see this error:
10 |
11 | Cannot deserialize the contract state.
12 |
13 | Why does this happen?
14 |
15 | When your contract is executed, the NEAR Runtime reads the serialized state from disk and attempts to load it using current contract code. When your code changes but the serialized state stays the same, it can't figure out how to do this.
16 |
17 | How can you avoid such errors?
18 |
19 | When you're still in the Research & Development phase, building a prototype and deploying it locally or on [testnet](https://docs.near.org/concepts/basics/networks), you can just delete all previous contract state when you make a breaking change. See below for a couple ways to do this.
20 |
21 | When you're ready to deploy a more stable contract, there are a couple [production strategies](./production-basics.md) that will help you update contract state without deleting it all. And once your contract graduates from "trusted mode" (when maintainers control a [Full Access key](https://docs.near.org/concepts/basics/account#access-keys)) to community-governed mode (no more Full Access keys), you'll need to know how to upgrade your contract code itself [via a DAO vote](./via-dao-vote.md).
22 |
23 |
24 | ## Rapid Prototyping: Delete Everything All The Time
25 |
26 | There are two ways to delete all account state:
27 |
28 | 1. `rm -rf neardev && near dev-deploy`
29 | 2. Deleting & recreating contract account
30 |
31 | For both cases, let's consider the following example.
32 |
33 | The [rust-status-message](https://github.com/near-examples/rust-status-message) example contract has the following structure:
34 |
35 | ```rust reference
36 | https://github.com/near-examples/rust-status-message/blob/b5fa6f2a30559d56a3a3ea52da8c26c5d3907606/src/lib.rs#L5-L29
37 | ```
38 |
39 | Let's say you deploy this contract to testnet, then call it with:
40 |
41 | ```bash
42 | near call [contract] set_status '{"message": "lol"}' --accountId you.testnet
43 | near view [contract] get_status '{"account_id": "you.testnet"}'
44 | ```
45 |
46 | This will return the message that you set with the call to `set_status`, in this case `"lol"`.
47 |
48 | At this point the contract is deployed and has some state.
49 |
50 | Now let's say you change the contract to store two kinds of data for each account:
51 |
52 | ```rust
53 | #[near_bindgen]
54 | #[derive(BorshDeserialize, BorshSerialize)]
55 | pub struct StatusMessage {
56 | taglines: LookupMap,
57 | bios: LookupMap,
58 | }
59 |
60 | impl Default for StatusMessage {
61 | fn default() -> Self {
62 | Self {
63 | taglines: LookupMap::new(b"r"),
64 | bios: LookupMap::new(b"b"),
65 | }
66 | }
67 | }
68 |
69 | #[near_bindgen]
70 | impl StatusMessage {
71 | pub fn set_tagline(&mut self, message: String) {
72 | let account_id = env::signer_account_id();
73 | self.taglines.insert(&account_id, &message);
74 | }
75 |
76 | pub fn get_tagline(&self, account_id: AccountId) -> Option {
77 | return self.taglines.get(&account_id);
78 | }
79 |
80 | pub fn set_bio(&mut self, message: String) {
81 | let account_id = env::signer_account_id();
82 | self.bios.insert(&account_id, &message);
83 | }
84 |
85 | pub fn get_bio(&self, account_id: AccountId) -> Option {
86 | return self.bios.get(&account_id);
87 | }
88 | }
89 | ```
90 |
91 | You build & deploy the contract again, thinking that maybe because the new `taglines` LookupMap has the same prefix as the old `records` LookupMap (the prefix is `r`, set by `LookupMap::new(b"r".to_vec())`), the tagline for `you.testnet` should be `"lol"`. But when you `near view` the contract, you get the "Cannot deserialize" message. What to do?
92 |
93 | ### 1. `rm -rf neardev && near dev-deploy`
94 |
95 | When first getting started with a new project, the fastest way to deploy a contract is [`dev-deploy`](https://docs.near.org/docs/concepts/account#how-to-create-a-dev-account):
96 |
97 | ```bash
98 | near dev-deploy [--wasmFile ./path/to/compiled.wasm]
99 | ```
100 |
101 | This does a few things:
102 |
103 | 1. Creates a new testnet account with a name like `dev-1626793583587-89195915741581`
104 | 2. Stores this account name in a `neardev` folder within the project
105 | 3. Stores the private key for this account in the `~/.near-credentials` folder
106 | 4. Deploys your contract code to this account
107 |
108 | The next time you run `dev-deploy`, it checks the `neardev` folder and re-deploys to the same account rather than making a new one.
109 |
110 | But in the example above, we want to delete the account state. How do we do that?
111 |
112 | The easiest way is just to delete the `neardev` folder, then run `near dev-deploy` again. This will create a brand new testnet account, with its own (empty) state, and deploy the updated contract to it.
113 |
114 | ### 2. Deleting & recreating contract account
115 |
116 | If you want to have a predictable account name rather than an ever-changing `dev-*` account, the best way is probably to create a sub-account:
117 |
118 | ```bash title="Create sub-account"
119 | near create-account app-name.you.testnet --masterAccount you.testnet
120 | ```
121 |
122 | Then deploy your contract to it:
123 |
124 | ```bash title="Deploy to sub-account"
125 | near deploy --accountId app-name.you.testnet [--wasmFile ./path/to/compiled.wasm]
126 | ```
127 |
128 | In this case, how do you delete all contract state and start again? Delete the sub-account and recreate it.
129 |
130 | ```bash title="Delete sub-account"
131 | near delete app-name.you.testnet you.testnet
132 | ```
133 |
134 | This sends all funds still on the `app-name.you.testnet` account to `you.testnet` and deletes the contract that had been deployed to it, including all contract state.
135 |
136 | Now you create the sub-account and deploy to it again using the commands above, and it will have empty state like it did the first time you deployed it.
137 |
--------------------------------------------------------------------------------
/docs/contract-interface/serialization-interface.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 5
3 | ---
4 |
5 | # Serialization Protocols
6 |
7 | Serialization formats within the SDK define how data structures are translated into bytes which are needed for passing data into methods of the smart contract or storing data in state. For the case of method parameters, [JSON](https://www.json.org/json-en.html) (default) and [Borsh](https://borsh.io/) are supported with the SDK and for storing data on-chain Borsh is used.
8 |
9 | The qualities of JSON and Borsh are as follows:
10 |
11 | JSON:
12 | - Human-readable
13 | - Self-describing format (don't need to know the underlying type)
14 | - Easy interop with JavaScript
15 | - Less efficient size and (de)serialization
16 |
17 | Borsh:
18 | - Compact, binary format that's efficient for serialized data size
19 | - Need to know data format or have a schema to deserialize data
20 | - Strict and canonical binary representation
21 | - Fast and less overhead in most cases
22 |
23 | In general, JSON will be used for contract calls and cross-contract calls for a better DevX, where Borsh can be used to optimize using less gas by having smaller parameter serialization and less deserialization computation within the contract.
24 |
25 | ### Overriding Serialization Protocol Default
26 |
27 | The result and parameter serialization can be opted into separately, but all parameters must be of the same format (can't serialize some parameters as borsh and others as JSON). An example of switching both the result and parameters to borsh is as follows:
28 |
29 | ```rust
30 | #[result_serializer(borsh)]
31 | pub fn sum_borsh(#[serializer(borsh)] a: u32, #[serializer(borsh)] b: u32) -> u32 {
32 | a + b
33 | }
34 | ```
35 |
36 | Where the `result_serializer(borsh)` annotation will override the default result serialization protocol from JSON to borsh and the `serializer(borsh)` annotations will override the parameter serialization.
37 |
38 | #### Example
39 |
40 | A simple demonstration of getting a [Borsh-serialized](https://borsh.io), base64-encoded value from a unit test:
41 |
42 | ```rust reference
43 | https://github.com/mikedotexe/rust-status-message/blob/b83c5126fdbe0f19bc904e547fda0bb12c2ea133/src/lib.rs#L93-L104
44 | ```
45 |
46 | The following snippet shows a simple function that takes this value from a frontend or CLI. Note: this method doesn't have a return value, so the `#[result_serializer(borsh)]` isn't needed.
47 |
48 | ```rust reference
49 | https://github.com/mikedotexe/rust-status-message/blob/b83c5126fdbe0f19bc904e547fda0bb12c2ea133/src/lib.rs#L40-L42
50 | ```
51 |
52 | Note that this is using this simple struct:
53 |
54 | ```rust reference
55 | https://github.com/mikedotexe/rust-status-message/blob/b83c5126fdbe0f19bc904e547fda0bb12c2ea133/src/lib.rs#L13-L17
56 | ```
57 |
58 | To call this with NEAR CLI, use a command similar to this:
59 |
60 | near call rust-status-message.demo.testnet set_status_borsh --base64 'DAAAAEFsb2hhIGhvbnVhIQ==' --accountId demo.testnet
61 |
62 | See more details in [this GitHub gist](https://gist.github.com/mfornet/d8a94af333a68d67affd8cb78464c7c0) from [Marcelo](https://gist.github.com/mfornet).
63 |
64 | ### JSON wrapper types
65 |
66 | To help with serializing certain types to JSON which have unexpected or inefficient default formats, there are some wrapper types in [`near_sdk::json_types`](https://docs.rs/near-sdk/3.1.0/near_sdk/json_types/index.html) that can be used.
67 |
68 | Because JavaScript only supports integers to value `2^53 - 1`, you will lose precision if deserializing the JSON integer is above this range. To counteract this, you can use the `I64`, `U64`, `I128`, and `U128` in place of the native types for these parameters or result to serialize the value as a string. By default, all integer types will serialize as an integer in JSON.
69 |
70 | You can convert from `U64` to `u64` and back using `std::convert::Into`, e.g.
71 |
72 | ```rust
73 | #[near_bindgen]
74 | impl Contract {
75 | pub fn mult(&self, a: U64, b: U64) -> U128 {
76 | let a: u64 = a.into();
77 | let b: u64 = b.into();
78 | let product = u128::from(a) * u128::from(b);
79 | product.into()
80 | }
81 | }
82 | ```
83 |
84 | You can also access inner values and using `.0`:
85 |
86 | ```diff
87 | #[near_bindgen]
88 | impl Contract {
89 | pub fn mult(&self, a: U64, b: U64) -> U128 {
90 | - let a: u64 = a.into();
91 | + let a = a.0;
92 | - let b: u64 = b.into();
93 | + let b = b.0;
94 | let product = u128::from(a) * u128::from(b);
95 | product.into()
96 | }
97 | }
98 | ```
99 |
100 | And you can cast the lower-case `u` variants to upper-case `U` variants using `U64(...)` and `U128(...)`:
101 |
102 | ```diff
103 | #[near_bindgen]
104 | impl Contract {
105 | pub fn mult(&self, a: U64, b: U64) -> U128 {
106 | let a = a.0;
107 | let b = b.0;
108 | let product = u128::from(a) * u128::from(b);
109 | - product.into()
110 | + U128(product)
111 | }
112 | }
113 | ```
114 |
115 | Combining it all:
116 |
117 | ```rust
118 | #[near_bindgen]
119 | impl Contract {
120 | pub fn mult(&self, a: U64, b: U64) -> U128 {
121 | U128(u128::from(a.0) * u128::from(b.0))
122 | }
123 | }
124 | ```
125 |
126 | Although there are these JSON wrapper types included with the SDK, any custom type can be used, as long as it implements [`serde`](https://serde.rs/) serialize and deserialize respectively. All of these types just override the JSON format and will have a consistent `borsh` serialization and deserialization as the inner types.
127 |
128 | ### Base64VecU8
129 |
130 | Another example of a type you may want to override the default serialization of is `Vec` which represents bytes in Rust. By default, this will serialize as an array of integers, which is not compact and very hard to use. There is a wrapper type [`Base64VecU8`](https://docs.rs/near-sdk/3.1.0/near_sdk/json_types/struct.Base64VecU8.html) which serializes and deserializes to a [Base-64](https://en.wikipedia.org/wiki/Base64) string for more compact JSON serialization.
131 |
132 | Example here:
133 |
134 | ```rust
135 | #[near_bindgen]
136 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
137 | pub struct Contract {
138 | // Notice, internally we store `Vec`
139 | pub data: Vec,
140 | }
141 | #[near_bindgen]
142 | impl Contract {
143 | #[init]
144 | pub fn new(data: Base64VecU8) -> Self {
145 | Self {
146 | data: data.into(),
147 | }
148 | }
149 | pub fn get_data(self) -> Base64VecU8 {
150 | self.data.into()
151 | }
152 | }
153 | ```
--------------------------------------------------------------------------------
/docs/cross-contract/callbacks.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # Callbacks
6 |
7 | NEAR Protocol is a sharded, proof-of-stake blockchain that behaves differently than proof-of-work blockchains. When interacting with a native Rust (compiled to Wasm) smart contract, cross-contract calls are asynchronous. Callbacks are used to either get the result of a cross-contract call or tell if a cross-contract call has succeeded or failed.
8 |
9 | There are two techniques to write cross-contract calls: [high-level](https://github.com/near/near-sdk-rs/blob/master/examples/cross-contract-calls/high-level/src/lib.rs) and [low-level](https://github.com/near/near-sdk-rs/blob/master/examples/cross-contract-calls/low-level/src/lib.rs). This document will mostly focus on the high-level approach. There are two examples in the Rust SDK repository that demonstrate these, as linked above. Note that these examples use cross-contract calls "to itself." We'll show two examples demonstrating the high-level approach.
10 |
11 | ## Calculator example
12 |
13 | There is a helper macro that allows you to make cross-contract calls with the syntax `#[ext_contract(...)]`. It takes a Rust Trait and converts it to a module with static methods. Each of these static methods takes positional arguments defined by the Trait, then the `receiver_id`, the attached deposit and the amount of gas and returns a new `Promise`.
14 |
15 | For example, let's define a calculator contract Trait:
16 |
17 | ```rust
18 | #[ext_contract(ext_calculator)]
19 | trait Calculator {
20 | fn mult(&self, a: U64, b: U64) -> U128;
21 |
22 | fn sum(&self, a: U128, b: U128) -> U128;
23 | }
24 | ```
25 |
26 | It's equivalent to the following code:
27 |
28 | ```rust
29 | mod ext_calculator {
30 | pub fn mult(a: U64, b: U64, receiver_id: &AccountId, deposit: Balance, gas: Gas) -> Promise {
31 | Promise::new(receiver_id.clone())
32 | .function_call(
33 | b"mult",
34 | json!({ "a": a, "b": b }).to_string().as_bytes(),
35 | deposit,
36 | gas,
37 | )
38 | }
39 |
40 | pub fn sum(a: U128, b: U128, receiver_id: &AccountId, deposit: Balance, gas: Gas) -> Promise {
41 | // ...
42 | }
43 | }
44 | ```
45 |
46 | Let's assume the calculator is deployed on `calc.near`, we can use the following:
47 |
48 | ```rust
49 | #[near_bindgen]
50 | impl Contract {
51 | pub fn sum_a_b(&mut self, a: U128, b: U128) -> Promise {
52 | let calculator_account_id: AccountId = "calc.near".parse().unwrap();
53 | // Call the method `sum` on the calculator contract.
54 | // Any unused GAS will be attached since the default GAS weight is 1.
55 | // Attached deposit is defaulted to 0.
56 | ext_calculator::ext(calculator_account_id)
57 | .sum(a, b)
58 | }
59 | }
60 | ```
61 |
62 | ## Allowlist example
63 |
64 | Next we'll look at a simple cross-contract call that is made to an allowlist smart contract, returning whether an account is in the list or not.
65 |
66 | The common pattern with cross-contract calls is to call a method on an external smart contract, use `.then` syntax to specify a callback, and then retrieve the result or status of the promise. The callback will typically live inside the same, calling smart contract. There's a special macro used for the callback function, which is [#[private]](https://docs.rs/near-sdk-core/latest/near_sdk_core/struct.AttrSigInfo.html#structfield.is_private). We'll see this pattern in the example below.
67 |
68 | The following example demonstrates two common approaches to callbacks using the high-level cross-contract approach. When writing high-level cross-contract calls, special [traits](https://doc.rust-lang.org/rust-by-example/trait.html) are set up as interfaces for the smart contract being called.
69 |
70 | ```rust
71 | #[ext_contract(ext_allowlist)]
72 | pub trait ExtAllowlist {
73 | fn is_allowlisted(staking_pool_account_id: AccountId) -> bool;
74 | }
75 | ```
76 |
77 | After creating the trait, we'll show two simple functions that will make a cross-contract call to an allowlist smart contract, asking if the account `mike.testnet` is allowlisted. These methods will both return `true` using different approaches. First we'll look at the methods, then we'll look at the differences in callbacks. Note that for simplicity in this example, the values are hardcoded.
78 |
79 | ```rust
80 | pub const XCC_GAS: Gas = Gas(20000000000000);
81 | fn get_allowlist_contract() -> AccountId {
82 | "allowlist.demo.testnet".parse().unwrap()
83 | }
84 | fn get_account_to_check() -> AccountId {
85 | "mike.testnet".parse().unwrap()
86 | }
87 | ```
88 |
89 | ```rust
90 | #[near_bindgen]
91 | impl Contract {
92 | pub fn xcc_use_promise_result() -> Promise {
93 | // Call the method `is_allowlisted` on the allowlisted contract. Static GAS is only attached to the callback.
94 | // Any unused GAS will be split between the function call and the callback since both have a default unused GAS weight of 1
95 | // Attached deposit is defaulted to 0 for both the function call and the callback.
96 | ext_allowlist::ext(get_allowlist_contract())
97 | .is_allowlisted(get_account_to_check())
98 | .then(
99 | Self::ext(env::current_account_id())
100 | .with_static_gas(XCC_GAS)
101 | .callback_promise_result()
102 | )
103 | }
104 |
105 | pub fn xcc_use_arg_macro(&mut self) -> Promise {
106 | // Call the method `is_allowlisted` on the allowlisted contract. Attach static GAS equal to XCC_GAS only for the callback.
107 | // Any unused GAS will be split between the function call and the callback since both have a default unused GAS weight of 1
108 | // Attached deposit is defaulted to 0 for both the function call and the callback.
109 | ext_allowlist::ext(get_allowlist_contract())
110 | .is_allowlisted(get_account_to_check())
111 | .then(
112 | Self::ext(env::current_account_id())
113 | .with_static_gas(XCC_GAS)
114 | .callback_arg_macro()
115 | )
116 | }
117 | ```
118 |
119 | The syntax begins with `ext_allowlist::ext()` showing that we're using the trait to call the method on the account passed into `ext()`. We then use `with_static_gas()` to specify a base amount of GAS to attach to the call. We then call the method `is_allow_listed()` and pass in the parameters we'd like to attach.
120 |
121 | There are a couple things to note when doing these function calls:
122 | 1. You can attach a deposit of Ⓝ, in yoctoⓃ to the call by specifying the `.with_attached_deposit()` method but it is defaulted to 0 (1 Ⓝ = 1000000000000000000000000 yoctoⓃ, or 1^24 yoctoⓃ).
123 | 2. You can attach a static amount of GAS by specifying the `.with_static_gas()` method but it is defaulted to 0.
124 | 3. You can attach an unused GAS weight by specifying the `.with_unused_gas_weight()` method but it is defaulted to 1. The unused GAS will be split amongst all the functions in the current execution depending on their weights. If there is only 1 function, any weight above 1 will result in all the unused GAS being attached to that function. If you specify a weight of 0, however, the unused GAS will **not** be attached to that function. If you have two functions, one with a weight of 3, and one with a weight of 1, the first function will get `3/4` of the unused GAS and the other function will get `1/4` of the unused GAS.
125 |
126 | The two methods in the snippet above are very similar, except they will call separate callbacks in the smart contract, `callback_promise_result` and `callback_arg_macro`. These two callbacks show how a value can be obtained.
127 |
128 | ```rust
129 | #[private]
130 | pub fn callback_arg_macro(#[callback_unwrap] val: bool) -> bool {
131 | val
132 | }
133 |
134 | #[private]
135 | pub fn callback_promise_result() -> bool {
136 | assert_eq!(env::promise_results_count(), 1, "ERR_TOO_MANY_RESULTS");
137 | match env::promise_result(0) {
138 | PromiseResult::NotReady => unreachable!(),
139 | PromiseResult::Successful(val) => {
140 | if let Ok(is_allowlisted) = near_sdk::serde_json::from_slice::(&val) {
141 | is_allowlisted
142 | } else {
143 | env::panic_str("ERR_WRONG_VAL_RECEIVED")
144 | }
145 | },
146 | PromiseResult::Failed => env::panic_str("ERR_CALL_FAILED"),
147 | }
148 | }
149 | ```
150 |
151 | The first method uses a macro on the argument to cast the value into what's desired. In this approach, if the value is unable to be casted, it will panic. If you'd like to gracefully handle the error, you can either use the first approach, or use the `#[callback_result]` macro instead. An example of this can be seen below.
152 |
153 | ```rust
154 | #[private]
155 | pub fn handle_callbacks(
156 | // New pattern, will gracefully handle failed callback results
157 | #[callback_result] b: Result,
158 | ) {
159 | if b.is_err() {
160 | // ...
161 | }
162 | }
163 | ```
164 |
165 | The second method gets the value from the promise result and is essentially the expanded version of the `#[callback_result]` macro.
166 |
167 | And that's it! Understanding how to make a cross-contract call and receive a result is an important part of developing smart contracts on NEAR. Two interesting references for using cross-contract calls can be found in the [fungible token](https://github.com/near-examples/FT) and [non-fungible token](https://github.com/near-examples/NFT) examples.
168 |
--------------------------------------------------------------------------------
/docs/upgrading/production-basics.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | sidebar_label: Production App Basics
4 | title: "Upgrading Contracts: Production App Basics"
5 | ---
6 |
7 | # Production App Basics
8 |
9 | When deploying new code to production contracts, you obviously can't destroy old account state, as you do [during rapid prototyping](./prototyping.md). So how to you prevent the dreaded error?
10 |
11 | Cannot deserialize the contract state.
12 |
13 | You can use a couple different approaches, depending on the complexity of your contract.
14 |
15 | ## Migration method
16 |
17 | For cases like [the change to the `rust-status-message` contract](https://github.com/near-examples/rust-status-message/commit/a39e1fc55ee018b631e3304ba6f0884b7558873e) that we looked at [previously](./prototyping.md), a simple migration method is all you need.
18 |
19 | As a reminder, the goal was to change this:
20 |
21 | ```rust reference
22 | https://github.com/near-examples/rust-status-message/blob/b5fa6f2a30559d56a3a3ea52da8c26c5d3907606/src/lib.rs#L7-L17
23 | ```
24 |
25 | into this:
26 |
27 | ```rust
28 | pub struct StatusMessage {
29 | taglines: LookupMap,
30 | bios: LookupMap,
31 | }
32 |
33 | impl Default for StatusMessage {
34 | fn default() -> Self {
35 | Self {
36 | taglines: LookupMap::new(b"r".to_vec()),
37 | bios: LookupMap::new(b"b".to_vec()),
38 | }
39 | }
40 | }
41 | ```
42 |
43 | The NEAR Runtime looks at your current code as well as your contract's data, which is serialized and saved on-disk. When it executes the code, it tries to match these up. If you change the code but the data stays the same, it can't figure out how to do this. Previously we "solved" this by removing old serialized data. Now let's see how to update the data instead.
44 |
45 | First, keep the old `struct` around for at least one deploy:
46 |
47 | ```rust
48 | #[derive(BorshDeserialize, BorshSerialize)]
49 | pub struct OldStatusMessage {
50 | records: LookupMap,
51 | }
52 |
53 | ```
54 |
55 | And add a `migrate` method to the main struct:
56 |
57 | ```rust reference
58 | https://github.com/near-examples/rust-status-message/blob/7f6afcc5ce414271fdf9bc750f666c062a6d697e/src/lib.rs#L48-L56
59 | ```
60 |
61 | :::note Need a refresher?
62 |
63 | Click here to see the full diff between the starting contract and the update + migration.
64 |
65 | ```diff
66 | +#[derive(BorshDeserialize, BorshSerialize)]
67 | +pub struct OldStatusMessage {
68 | + records: LookupMap,
69 | +}
70 | +
71 | #[near_bindgen]
72 | #[derive(BorshDeserialize, BorshSerialize)]
73 | pub struct StatusMessage {
74 | - records: LookupMap,
75 | + taglines: LookupMap,
76 | + bios: LookupMap,
77 | }
78 |
79 | impl Default for StatusMessage {
80 | fn default() -> Self {
81 | Self {
82 | - records: LookupMap::new(b"r".to_vec()),
83 | + taglines: LookupMap::new(b"r".to_vec()),
84 | + bios: LookupMap::new(b"b".to_vec()),
85 | }
86 | }
87 | }
88 |
89 | #[near_bindgen]
90 | impl StatusMessage {
91 | - pub fn set_status(&mut self, message: String) {
92 | + pub fn set_tagline(&mut self, message: String) {
93 | let account_id = env::signer_account_id();
94 | - self.records.insert(&account_id, &message);
95 | + self.taglines.insert(&account_id, &message);
96 | + }
97 | +
98 | + pub fn get_tagline(&self, account_id: String) -> Option {
99 | + return self.taglines.get(&account_id);
100 | }
101 |
102 | - pub fn get_status(&self, account_id: String) -> Option {
103 | - return self.records.get(&account_id);
104 | + pub fn set_bio(&mut self, message: String) {
105 | + let account_id = env::signer_account_id();
106 | + self.bios.insert(&account_id, &message);
107 | + }
108 | +
109 | + pub fn get_bio(&self, account_id: String) -> Option {
110 | + return self.bios.get(&account_id);
111 | + }
112 | +
113 | + #[private]
114 | + #[init(ignore_state)]
115 | + pub fn migrate() -> Self {
116 | + let old_state: OldStatusMessage = env::state_read().expect("failed");
117 | + Self {
118 | + taglines: old_state.records,
119 | + bios: LookupMap::new(b"b".to_vec()),
120 | + }
121 | }
122 | }
123 | ```
124 |
125 | :::
126 |
127 | When you deploy your change, call the `migrate` method:
128 |
129 | near deploy \
130 | --wasmFile res/status_message.wasm \
131 | --initFunction "migrate" \
132 | --initArgs "{}" \
133 | --accountId app-name.you.testnet
134 |
135 | Finally, you can view old statuses with your new `get_tagline` method:
136 |
137 | near view app-name.you.testnet get_tagline '{"account_id": "you.testnet"}'
138 |
139 | Hooray!
140 |
141 | :::tip Tidying Up
142 | At this point, all contract state has been migrated, and you don't need to keep the `OldStatusMessage` struct or the `migrate` method. Feel free to remove them and deploy again with no `initFunction` call. Your contract will be all tidy and ready for the next migration!
143 | :::
144 |
145 | ## Using Enums
146 |
147 | In the example above, all contract state is stored in one simple struct. Many real-world contracts are more complex, often having one struct referenced by another. For example, a [DAO](https://whiteboardcrypto.com/what-is-a-dao/) contract might look something like this:
148 |
149 | ```rust
150 | #[derive(BorshSerialize, BorshDeserialize)]
151 | pub enum ProposalStatus {
152 | Proposed,
153 | Approved,
154 | Rejected,
155 | }
156 |
157 | #[derive(BorshSerialize, BorshDeserialize)]
158 | pub struct Proposal {
159 | pub description: String,
160 | pub status: ProposalStatus,
161 | }
162 |
163 | #[near_bindgen]
164 | #[derive(BorshSerialize, BorshDeserialize)]
165 | pub struct DAO {
166 | pub proposals: LookupMap,
167 | }
168 | ```
169 |
170 | :::note
171 | For a more complete DAO example, check out [SputnikDAO](https://github.com/near-daos/sputnik-dao-contract/blob/317ea4fb1e6eac8064ef29a78054b0586a3406c3/sputnikdao2/src/lib.rs), [Flux](https://github.com/fluxprotocol/amm/blob/3def886a7fbd2df4ba28e18f67e6ab12cd2eee0b/dao/src/lib.rs), and [others](https://github.com/search?q=near+dao).
172 | :::
173 |
174 | Say you want to update the structure of `Proposal` but keep `DAO` unchanged.
175 |
176 | The first thing to note is that the contract could be storing a huge number of proposals, which makes it impossible to migrate all of them in one transaction due to [the gas limit](https://docs.near.org/concepts/basics/transactions/gas#thinking-in-gas). In an off-chain script, you could query the full state of the contract and update every single one of them via multiple transactions. But that may be prohibitively expensive, so you might opt to upgrade proposals to the new structure during the next interaction with them, rather than all at once (this disperses the upgrade cost to users of the contract).
177 |
178 | In either case, your contract can end up with proposals using the original structure and the new structure at the same time, and the `DAO` struct needs to know how to load both of them. How do you do that?
179 |
180 | Use [enums](https://doc.rust-lang.org/book/ch06-00-enums.html):
181 |
182 | ```rust
183 | #[derive(BorshSerialize, BorshDeserialize)]
184 | pub enum ProposalStatus {
185 | Proposed,
186 | Approved,
187 | Rejected,
188 | }
189 |
190 | #[derive(BorshSerialize, BorshDeserialize)]
191 | pub struct ProposalV1 {
192 | pub description: String,
193 | pub status: ProposalStatus,
194 | }
195 |
196 | #[derive(BorshSerialize, BorshDeserialize)]
197 | pub struct Proposal {
198 | pub title: String,
199 | pub description: String,
200 | pub status: ProposalStatus,
201 | }
202 |
203 | #[derive(BorshSerialize, BorshDeserialize)]
204 | pub enum UpgradableProposal {
205 | V1(ProposalV1),
206 | V2(Proposal),
207 | }
208 |
209 | impl From for Proposal {
210 | fn from(proposal: UpgradableProposal) -> Self {
211 | match proposal {
212 | UpgradableAccount::V2(proposal) => proposal,
213 | UpgradableAccount::V1(v1) => Proposal {
214 | // set title to first 10 chars of description
215 | title: v1.description.get(..10).map(str::to_owned).unwrap_or_default(),
216 | description: v1.description,
217 | status: v1.status,
218 | }
219 | }
220 | }
221 | }
222 |
223 | #[near_bindgen]
224 | #[derive(BorshSerialize, BorshDeserialize)]
225 | pub struct DAO {
226 | pub proposals: LookupMap,
227 | }
228 | ```
229 |
230 | :::danger Untested Example
231 | The example above is not tested and may contain bugs or be incomplete.
232 |
233 | Someone (us? you??) needs to create a full example repository that clearly demonstrates this upgrade path, and link to it in the snippets above.
234 |
235 | In the meantime, you can see working examples and learn more about this pattern at the following links:
236 |
237 | * https://github.com/evgenykuzyakov/berryclub/commit/d78491b88cbb16a79c15dfc3901e5cfb7df39fe8
238 | * https://nomicon.io/ChainSpec/Upgradability.html
239 | * https://github.com/mikedotexe/rust-contract-upgrades/pulls
240 | :::
241 |
242 |
243 | ## Writing Upgradable Contracts
244 |
245 | If you plan to upgrade your contracts throughout their lifetime, **start with enums**. Adding them only after you decide to upgrade is (usually) possible, but will result in harder-to-follow (and thus more error-prone) code.
246 |
--------------------------------------------------------------------------------
/docs/testing/integration-tests.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # Integration Tests
6 |
7 | **Note:** Simulation tests are no longer actively supported. NEAR Simulator was meant to be an in-place replacement of a blockchain environment for the purpose of testing NEAR contracts. However, simulating NEAR ledger turned out to be a much more complex endeavour than was anticipated. Eventually, the idea of workspaces was born - a library for automating workflows and writing tests for NEAR smart contracts using a real NEAR network (localnet, testnet or mainnet). Thus, NEAR Simulator is being deprecated in favor of [`workspaces-rs`](https://github.com/near/workspaces-rs), the Rust edition of workspaces. As the two libraries have two vastly different APIs [this guide](workspaces-migration-guide.md) was created to ease the migration process for developers.
8 |
9 | ## Unit Tests vs. Integration Tests
10 |
11 | Unit tests are great for ensuring that functionality works as expected at an insolated, functional-level. This might include checking that function `get_nth_fibonacci(n: u8)` works as expected, handles invalid input gracefully, etc. Unit tests in smart contracts might similarly test public functions, but can get unruly if there are several calls between accounts. As mentioned in the [unit tests](unit-tests.md) section, there is a `VMContext` object used by unit tests to mock some aspects of a transaction. One might, for instance, modify the testing context to have the `predecessor_account_id` of `"bob.near"`. The limits of unit tests become obvious with certain interactions, like transferring tokens. Since `"bob.near"` is simply a string and not an account object, there is no way to write a unit test that confirms that Alice sent Bob 6 NEAR (Ⓝ). Furthermore, there is no way to write a unit test that executes cross-contract calls. Additionally, there is no way of profiling gas usage and the execution of the call (or set of calls) on the blockchain.
12 |
13 | Integration tests provide the ability to have end-to-end testing that includes cross-contract calls, proper user accounts, access to state, structured execution outcomes, and more. In NEAR, we can make use of the `workspaces` libraries in both [Rust](https://github.com/near/workspaces-rs) and [JavaScript](https://github.com/near/workspaces-js) for this type of testing on a locally-run blockchain or testnet.
14 |
15 | ## When to Use Integration Tests
16 |
17 | You'll probably want to use integration tests when:
18 |
19 | - There are cross-contract calls.
20 | - There are multiple users with balance changes.
21 | - You'd like to gather information about gas usage and execution outcomes on-chain.
22 | - You want to assert the use-case execution flow of your smart contract logic works as expected.
23 | - You want to assert given execution patterns do not work (as expected).
24 |
25 | ## Setup
26 |
27 | Unlike unit tests (which would often live in the `src/lib.rs` file of the contract), integration tests in Rust are located in a separate directory at the same level as `/src`, called `/tests` ([read more](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#integration-tests)). Refer to this folder structure below:
28 |
29 | ```sh
30 | ├── Cargo.toml ⟵ contains `dependencies` for contract and `dev-dependencies` for workspaces-rs tests
31 | ├── src
32 | │ └── lib.rs ⟵ contract code
33 | ├── target
34 | └── tests ⟵ integration test directory
35 | └── integration-tests.rs ⟵ integration test file
36 | ```
37 |
38 | :::info
39 | These tests don't have to be placed in their own `/tests` directory. Instead, you can place them in the `/src` directory which can be beneficial since then you can use the non-exported types for serialization within the test case.
40 | :::
41 |
42 | A sample configuration for this project's `Cargo.toml` is shown below:
43 |
44 | ```toml
45 | [package]
46 | name = "fungible-token-wrapper"
47 | version = "0.0.2"
48 | authors = ["Near Inc "]
49 | edition = "2021"
50 |
51 | [dev-dependencies]
52 | anyhow = "1.0"
53 | near-primitives = "0.5.0"
54 | near-sdk = "4.0.0"
55 | near-units = "0.2.0"
56 | serde_json = "1.0"
57 | tokio = { version = "1.14", features = ["full"] }
58 | workspaces = "0.4.1"
59 |
60 | # remember to include a line for each contract
61 | fungible-token = { path = "./ft" }
62 | defi = { path = "./test-contract-defi" }
63 |
64 | [profile.release]
65 | codegen-units = 1
66 | # Tell `rustc` to optimize for small code size.
67 | opt-level = "z"
68 | lto = true
69 | debug = false
70 | panic = "abort"
71 | overflow-checks = true
72 |
73 | [workspace]
74 | # remember to include a member for each contract
75 | members = [
76 | "ft",
77 | "test-contract-defi",
78 | ]
79 | ```
80 |
81 | The `integration-tests.rs` file above will contain the integration tests. These can be run with the following command from the same level as the test `Cargo.toml` file:
82 |
83 | cargo test --test integration-tests
84 |
85 | ## Comparing an Example
86 |
87 | ### Unit Test
88 |
89 | Let's take a look at a very simple unit test and integration test that accomplish the same thing. Normally you wouldn't duplicate efforts like this (as integration tests are intended to be broader in scope), but it will be informative.
90 |
91 | We'll be using snippets from the [fungible-token example](https://github.com/near/near-sdk-rs/blob/master/examples/fungible-token) from the `near-sdk-rs` repository to demonstrate simulation tests.
92 |
93 | First, note this unit test that tests the functionality of the `test_transfer` method:
94 |
95 | ```rust reference
96 | https://github.com/near/near-sdk-rs/blob/6d4045251c63ec875dc55f43b065b33a36d94792/examples/fungible-token/ft/src/lib.rs#L100-L165
97 | ```
98 |
99 | The test above sets up the testing context, instantiates the test environment through `get_context()`, calls the `test_transfer` method, and performs the `storage_deposit()` initialization call (to register with the fungible token contract) and the `ft_transfer()` fungible token transfer call.
100 |
101 | Let's look at how this might be written with workspaces tests. The snippet below is a bit longer as it demonstrates a couple of things worth noting.
102 |
103 | ### Workspaces Test
104 |
105 | ```rust reference
106 | https://github.com/near/near-sdk-rs/blob/master/examples/fungible-token/tests/workspaces.rs#L25-L115
107 | ```
108 |
109 | In the test above, the compiled smart contract `.wasm` file (which we compiled into the `/out` directory) for the Fungible Token example is dev-deployed (newly created account) to the environment. The `ft_contract` account is created as a result from the environment which is used to create accounts. This specific file's format has only one test entry point (`main`), and every test is declared with `#[tokio::test]`. Tests do not share state between runs.
110 |
111 | Notice the layout within `test_total_supply`. `.call()` obtains its required gas from the account performing it. Unlike the unit test, there is no mocking being performed before the call as the context is provided by the environment initialized during `init()`. Every call interacts with this environment to either fetch or change state.
112 |
113 | :::info
114 | **Pitfall**: you must compile your contract before running integration tests. Because workspaces tests use the `.wasm` files to deploy the contracts to the network. If changes are made to the smart contract code, the smart contract wasm should be rebuilt before running these tests again.
115 | :::
116 |
117 | :::note
118 | In case you wish to preserve state between runs, you can call multiple tests within one function, passing the worker around from a `workspaces::sandbox()` call.
119 | :::
120 |
121 | ## Helpful Snippets
122 |
123 | ### Create an Account
124 |
125 | ```rust reference
126 | https://github.com/near-examples/rust-counter/blob/6a7af5a32c630e0298c09c24eab87267746552b2/integration-tests/rs/src/tests.rs#L16-L21
127 | ```
128 |
129 | :::note
130 | You can also create a `dev_account` without having to deploy a contract as follows:
131 | ```rust reference
132 | https://github.com/near/workspaces-rs/blob/8f12f3dc3b0251ac3f44ddf6ab6fc63003579139/workspaces/tests/create_account.rs#L7-L8
133 | ```
134 | :::
135 |
136 | ### Create Helper Functions
137 |
138 | ```rust reference
139 | https://github.com/near-examples/nft-tutorial/blob/7fb267b83899d1f65f1bceb71804430fab62c7a7/integration-tests/rs/src/helpers.rs#L148-L161
140 | ```
141 |
142 | ### Spooning - Pulling Existing State and Contracts from Mainnet/Testnet
143 |
144 | This example showcases spooning state from a testnet contract into our local sandbox environment:
145 |
146 | ```rust reference
147 | https://github.com/near/workspaces-rs/blob/c14fe2aa6cdf586028b2993c6a28240f78484d3e/examples/src/spooning.rs#L64-L122
148 | ```
149 |
150 | For a full example, see the [examples/src/spooning.rs](https://github.com/near/workspaces-rs/blob/main/examples/src/spooning.rs) example.
151 |
152 | ### Fast Forwarding - Fast Forward to a Future Block
153 |
154 | `workspaces` testing offers support for forwarding the state of the blockchain to the future. This means contracts which require time sensitive data do not need to sit and wait the same amount of time for blocks on the sandbox to be produced. We can simply just call `worker.fast_forward` to get us further in time:
155 |
156 | ```rust reference
157 | https://github.com/near/workspaces-rs/blob/c14fe2aa6cdf586028b2993c6a28240f78484d3e/examples/src/fast_forward.rs#L12-L44
158 | ```
159 |
160 | For a full example, take a look at [examples/src/fast_forward.rs](https://github.com/near/workspaces-rs/blob/main/examples/src/fast_forward.rs).
161 |
162 | ### Handle Errors
163 |
164 | ```rust reference
165 | https://github.com/near-examples/FT/blob/98b85297a270cbcb8ef3901c29c17701e1cab698/integration-tests/rs/src/tests.rs#L199-L225
166 | ```
167 |
168 | :::note
169 | Returning `Err(msg)` is also a viable (and arguably simpler) implementation.
170 | :::
171 |
172 | ### Batch Transactions
173 |
174 | ```rust title="Batch Transaction - workspace-rs"
175 | let res = contract
176 | .batch(&worker)
177 | .call(
178 | Function::new("ft_transfer_call")
179 | .args_json((defi_contract.id(), transfer_amount, Option::::None, "10"))?
180 | .gas(300_000_000_000_000 / 2)
181 | .deposit(1),
182 | )
183 | .call(
184 | Function::new("storage_unregister")
185 | .args_json((Some(true),))?
186 | .gas(300_000_000_000_000 / 2)
187 | .deposit(1),
188 | )
189 | .transact()
190 | .await?;
191 | ```
192 |
193 | ### Inspecting Logs
194 |
195 | ```rust title="Logs - workspaces-rs"
196 | assert_eq!(
197 | res.logs()[1],
198 | format!("Closed @{} with {}", contract.id(), initial_balance.0 - transfer_amount.0)
199 | );
200 | ```
201 |
202 | Examining receipt outcomes:
203 |
204 | ```rust title="Logs - workspaces-rs"
205 | let outcome = &res.receipt_outcomes()[5];
206 | assert_eq!(outcome.logs[0], "The account of the sender was deleted");
207 | assert_eq!(outcome.logs[2], format!("Account @{} burned {}", contract.id(), 10));
208 | ```
209 |
210 | ### Profiling Gas
211 |
212 | `CallExecutionDetails::total_gas_burnt` includes all gas burnt by call execution, including by receipts. This is exposed as a surface level API since it is a much more commonly used concept:
213 |
214 | ```rust title="Gas (all) - workspaces-rs"
215 | println!("Burnt gas (all): {}", res.total_gas_burnt);
216 | ```
217 |
218 | If you do actually want gas burnt by transaction itself you can do it like this:
219 |
220 | ```rust title="Gas (transaction) - workspaces-rs"
221 | println!("Burnt gas (transaction): {}", res.outcome().gas_burnt);
222 | ```
223 |
224 | If you want to see the gas burnt by each receipt, you can do it like this:
225 |
226 | ```rust title="Gas (receipt) - workspaces-rs"
227 | for receipt in res.receipt_outcomes() {
228 | println!("Burnt gas (receipt): {}", receipt.gas_burnt);
229 | }
230 | ```
231 |
--------------------------------------------------------------------------------
/docs/contract-structure/collections.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # Collections
6 |
7 | When deciding on data structures to use for the data of the application, it is important to minimize the amount of data read and written to storage but also the amount of data serialized and deserialized to minimize the cost of transactions. It is important to understand the tradeoffs of data structures in your smart contract because it can become a bottleneck as the application scales and migrating the state to the new data structures will come at a cost.
8 |
9 | The collections within `near-sdk` are designed to split the data into chunks and defer reading and writing to the store until needed. These data structures will handle the low-level storage interactions and aim to have a similar API to the [`std::collections`](https://doc.rust-lang.org/std/collections/index.html).
10 |
11 | > Note: The `near_sdk::collections` will be moving to `near_sdk::store` and have updated APIs. If you would like to access these updated structures as they are being implemented, enable the `unstable` feature on `near-sdk`.
12 |
13 | It is important to keep in mind that when using `std::collections`, that each time state is loaded, all entries in the data structure will be read eagerly from storage and deserialized. This will come at a large cost for any non-trivial amount of data, so to minimize the amount of gas used the SDK collections should be used in most cases.
14 |
15 | The most up to date collections and their documentation can be found [in the rust docs](https://docs.rs/near-sdk/latest/near_sdk/collections/index.html).
16 |
17 |
18 | The following data structures that exist in the SDK are as follows:
19 |
20 | | SDK collection | `std` equivalent | Description |
21 | | ------------------------------------- | ------------------------------- | ------------|
22 | | `LazyOption` | `Option` | Optional value in storage. This value will only be read from storage when interacted with. This value will be `Some` when the value is saved in storage, and `None` if the value at the prefix does not exist. |
23 | | `Vector` | `Vec` | A growable array type. The values are sharded in memory and can be used for iterable and indexable values that are dynamically sized. |
24 | | LookupMap | HashMap | This structure behaves as a thin wrapper around the key-value storage available to contracts. This structure does not contain any metadata about the elements in the map, so it is not iterable. |
25 | | UnorderedMap | HashMap | Similar to `LookupMap`, except that it stores additional data to be able to iterate through elements in the data structure. |
26 | | TreeMap | BTreeMap | An ordered equivalent of `UnorderedMap`. The underlying implementation is based on an [AVL tree](https://en.wikipedia.org/wiki/AVL_tree). This structure should be used when a consistent order is needed or accessing the min/max keys is needed. |
27 | | `LookupSet` | `HashSet` | A set, which is similar to `LookupMap` but without storing values, can be used for checking the unique existence of values. This structure is not iterable and can only be used for lookups. |
28 | | `UnorderedSet` | `HashSet` | An iterable equivalent of `LookupSet` which stores additional metadata for the elements contained in the set. |
29 |
30 | ## In-memory `HashMap` vs persistent `UnorderedMap`
31 |
32 | - `HashMap` keeps all data in memory. To access it, the contract needs to deserialize the whole map.
33 | - `UnorderedMap` keeps data in persistent storage. To access an element, you only need to deserialize this element.
34 |
35 | Use `HashMap` in case:
36 |
37 | - Need to iterate over all elements in the collection **in one function call**.
38 | - The number of elements is small or fixed, e.g. less than 10.
39 |
40 | Use `UnorderedMap` in case:
41 |
42 | - Need to access a limited subset of the collection, e.g. one or two elements per call.
43 | - Can't fit the collection into memory.
44 |
45 | The reason is `HashMap` deserializes (and serializes) the entire collection in one storage operation.
46 | Accessing the entire collection is cheaper in gas than accessing all elements through `N` storage operations.
47 |
48 | Example of `HashMap`:
49 |
50 | ```rust
51 | /// Using Default initialization.
52 | #[near_bindgen]
53 | #[derive(BorshDeserialize, BorshSerialize, Default)]
54 | pub struct Contract {
55 | pub status_updates: HashMap,
56 | }
57 |
58 | #[near_bindgen]
59 | impl Contract {
60 | pub fn set_status(&mut self, status: String) {
61 | self.status_updates.insert(env::predecessor_account_id(), status);
62 | assert!(self.status_updates.len() <= 10, "Too many messages");
63 | }
64 |
65 | pub fn clear(&mut self) {
66 | // Effectively iterating through all removing them.
67 | self.status_updates.clear();
68 | }
69 |
70 | pub fn get_all_updates(self) -> HashMap {
71 | self.status_updates
72 | }
73 | }
74 | ```
75 |
76 | Example of `UnorderedMap`:
77 |
78 | ```rust
79 | #[near_bindgen]
80 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
81 | pub struct Contract {
82 | pub status_updates: UnorderedMap,
83 | }
84 |
85 | #[near_bindgen]
86 | impl Contract {
87 | #[init]
88 | pub fn new() -> Self {
89 | // Initializing `status_updates` with unique key prefix.
90 | Self {
91 | status_updates: UnorderedMap::new(b"s".to_vec()),
92 | }
93 | }
94 |
95 | pub fn set_status(&mut self, status: String) {
96 | self.status_updates.insert(&env::predecessor_account_id(), &status);
97 | // Note, don't need to check size, since `UnorderedMap` doesn't store all data in memory.
98 | }
99 |
100 | pub fn delete_status(&mut self) {
101 | self.status_updates.remove(&env::predecessor_account_id());
102 | }
103 |
104 | pub fn get_status(&self, account_id: AccountId) -> Option {
105 | self.status_updates.get(&account_id)
106 | }
107 | }
108 | ```
109 |
110 | ## Error prone patterns
111 |
112 | Because the values are not kept in memory and are lazily loaded from storage, it's important to make sure if a collection is replaced or removed, that the storage is cleared. In addition, it is important that if the collection is modified, the collection itself is updated in state because most collections will store some metadata.
113 |
114 | Some error-prone patterns to avoid that cannot be restricted at the type level are:
115 |
116 | ```rust
117 | use near_sdk::store::UnorderedMap;
118 |
119 | let mut m = UnorderedMap::::new(b"m");
120 | m.insert(1, "test".to_string());
121 | assert_eq!(m.len(), 1);
122 | assert_eq!(m.get(&1), Some(&"test".to_string()));
123 |
124 | // Bug 1: Should not replace any collections without clearing state, this will reset any
125 | // metadata, such as the number of elements, leading to bugs. If you replace the collection
126 | // with something with a different prefix, it will be functional, but you will lose any
127 | // previous data and the old values will not be removed from storage.
128 | m = UnorderedMap::new(b"m");
129 | assert!(m.is_empty());
130 | assert_eq!(m.get(&1), Some(&"test".to_string()));
131 |
132 | // Bug 2: Should not use the same prefix as another collection
133 | // or there will be unexpected side effects.
134 | let m2 = UnorderedMap::::new(b"m");
135 | assert!(m2.is_empty());
136 | assert_eq!(m2.get(&1), Some(&"test".to_string()));
137 |
138 | // Bug 3: forgetting to save the collection in storage. When the collection is attached to
139 | // the contract state (`self` in `#[near_bindgen]`) this will be done automatically, but if
140 | // interacting with storage manually or working with nested collections, this is relevant.
141 | use near_sdk::store::Vector;
142 |
143 | // Simulate roughly what happens during a function call that initializes state.
144 | {
145 | let v = Vector::::new(b"v");
146 | near_sdk::env::state_write(&v);
147 | }
148 |
149 | // Simulate what happens during a function call that just modifies the collection
150 | // but does not store the collection itself.
151 | {
152 | let mut v: Vector = near_sdk::env::state_read().unwrap();
153 | v.push(1);
154 | // The bug is here that the collection itself if not written back
155 | }
156 |
157 | let v: Vector = near_sdk::env::state_read().unwrap();
158 | // This will report as if the collection is empty, even though the element exists
159 | assert!(v.get(0).is_none());
160 | assert!(
161 | near_sdk::env::storage_read(&[b"v".as_slice(), &0u32.to_le_bytes()].concat()).is_some()
162 | );
163 |
164 | // Bug 4 (only relevant for `near_sdk::store`): These collections will cache writes as well
165 | // as reads, and the writes are performed on [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html)
166 | // so if the collection is kept in static memory, something like `std::mem::forget` is used,
167 | // the changes will not be persisted.
168 | use near_sdk::store::LookupSet;
169 |
170 | let mut m: LookupSet = LookupSet::new(b"l");
171 | m.insert(1);
172 | assert!(m.contains(&1));
173 |
174 | // This would be the fix, manually flushing the intermediate changes to storage.
175 | // m.flush();
176 | std::mem::forget(m);
177 |
178 | m = LookupSet::new(b"l");
179 | assert!(!m.contains(&1));
180 | }
181 | ```
182 |
183 | ## Pagination with persistent collections
184 |
185 | Persistent collections such as `UnorderedMap`, `UnorderedSet` and `Vector` may
186 | contain more elements than the amount of gas available to read them all.
187 | In order to expose them all through view calls, we can use pagination.
188 |
189 | This can be done using iterators with [`Skip`](https://doc.rust-lang.org/std/iter/struct.Skip.html) and [`Take`](https://doc.rust-lang.org/std/iter/struct.Take.html). This will only load elements from storage within the range.
190 |
191 | Example of pagination for `UnorderedMap`:
192 |
193 | ```rust
194 | #[near_bindgen]
195 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
196 | pub struct Contract {
197 | pub status_updates: UnorderedMap,
198 | }
199 |
200 | #[near_bindgen]
201 | impl Contract {
202 | /// Retrieves multiple elements from the `UnorderedMap`.
203 | /// - `from_index` is the index to start from.
204 | /// - `limit` is the maximum number of elements to return.
205 | pub fn get_updates(&self, from_index: usize, limit: usize) -> Vec<(AccountId, String)> {
206 | self.status_updates
207 | .iter()
208 | .skip(from_index)
209 | .take(limit)
210 | .collect()
211 | }
212 | }
213 | ```
214 |
215 | ## `LookupMap` vs `UnorderedMap`
216 |
217 | ### Functionality
218 |
219 | - `UnorderedMap` supports iteration over keys and values, and also supports pagination. Internally, it has the following structures:
220 | - a map from a key to an index
221 | - a vector of keys
222 | - a vector of values
223 | - `LookupMap` only has a map from a key to a value. Without a vector of keys, it doesn't have the ability to iterate over keys.
224 |
225 | ### Performance
226 |
227 | `LookupMap` has a better performance and stores less data compared to `UnorderedMap`.
228 |
229 | - `UnorderedMap` requires `2` storage reads to get the value and `3` storage writes to insert a new entry.
230 | - `LookupMap` requires only one storage read to get the value and only one storage write to store it.
231 |
232 | ### Storage space
233 |
234 | `UnorderedMap` requires more storage for an entry compared to a `LookupMap`.
235 |
236 | - `UnorderedMap` stores the key twice (once in the first map and once in the vector of keys) and value once. It also has a higher constant for storing the length of vectors and prefixes.
237 | - `LookupMap` stores key and value once.
238 |
239 | ## `LazyOption`
240 |
241 | It's a type of persistent collection that only stores a single value.
242 | The goal is to prevent a contract from deserializing the given value until it's needed.
243 | An example can be a large blob of metadata that is only needed when it's requested in a view call,
244 | but not needed for the majority of contract operations.
245 |
246 | It acts like an `Option` that can either hold a value or not and also requires a unique prefix (a key in this case)
247 | like other persistent collections.
248 |
249 | Compared to other collections, `LazyOption` only allows you to initialize the value during initialization.
250 |
251 | ```rust
252 | #[near_bindgen]
253 | #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
254 | pub struct Contract {
255 | pub metadata: LazyOption,
256 | }
257 |
258 | #[derive(Serialize, Deserialize, BorshDeserialize, BorshSerialize)]
259 | #[serde(crate = "near_sdk::serde")]
260 | pub struct Metadata {
261 | data: String,
262 | image: Base64Vec,
263 | blobs: Vec,
264 | }
265 |
266 | #[near_bindgen]
267 | impl Contract {
268 | #[init]
269 | pub fn new(metadata: Metadata) -> Self {
270 | Self {
271 | metadata: LazyOption::new(b"m", Some(metadata)),
272 | }
273 | }
274 |
275 | pub fn get_metadata(&self) -> Metadata {
276 | // `.get()` reads and deserializes the value from the storage.
277 | self.metadata.get().unwrap()
278 | }
279 | }
280 | ```
281 |
--------------------------------------------------------------------------------
/docs/testing/workspaces-migration-guide.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | sidebar_label: Workspaces Migration Guide
4 | title: "Migrating from Simulation Testing to Workspaces"
5 | ---
6 |
7 | # Migrating from Simulation Testing to Workspaces
8 |
9 | ### Why did we stop supporting Simulation Testing?
10 |
11 | Simulation tests were not suitable for purpose for a few reasons, namely:
12 |
13 | - `near-sdk-sim` was hooking into parts of nearcore that were not meant to be released, in the most recent version those crates aren't released so `near-sdk-sim` is currently using duplicate dependencies (maintenance nightmare).
14 | - Not a fully accurate simulation because it just used a subset of the runtime in a specific way - we can't rely on this. And thus couldn't measure gas burnt accurately. Also, all the intricacies of nearcore (like protocol features) wouldn't be one-to-one with the runtime since the runtime was just code built on top of VM logic. People would also need to write their own automation scripts to deploy to testnet, so we'd end up with very split workflows for testing.
15 | - Bulky dependencies pulled in (drastically increases compile time).
16 | - Unergonomic API, not specific to this strategy, but likely would have had to be re-built.
17 | - Can't test parallel transactions easily - current pattern would process blocks until a transaction succeeded but you can't create specific conditions, which is required for a strategy like this that isn't fully simulated.
18 |
19 | :::info
20 | This guide presumes that you are transitioning from near-sdk-sim `3.2.0` (the last non-deprecated release) to `workspaces-rs` `0.2.1`. Given that near-sdk-sim is deprecated, it is very unlikely that its API will ever change, but future releases of `workspaces-rs` might. Hopefully, this guide will be helpful even if you are migrating your project to a more recent workspaces version. If workspaces have changed, feel free to migrate your tests to `0.2.1` first using this guide and upgrade to the most recent workspaces-rs version later by looking at the release notes to see how public API has changed since `0.2.1`.
21 | :::
22 |
23 | ## Async Runtime and Error Handling
24 |
25 | In this section we will be working purely with test signatures, so it applies to pretty much all NEAR contract tests regardless of what is written inside. We will walk through each change one by one. Let's start with how your tests look like right now; chances are something like this:
26 |
27 | ```rust
28 | #[test]
29 | fn test_transfer() {
30 | ...
31 | }
32 | ```
33 |
34 | First big change is that `workspaces-rs` API is asynchronous, meaning that contract function calls return values that implement `Future` trait. You will not be able to operate on the call results in a synchronous environment, thus you will have to add an async runtime (if you do not already have one). In this guide we are going to be using [`tokio`](https://tokio.rs/), but you should be able to use any other alternative (e.g. [`async-std`](https://async.rs/), [`smol`](https://github.com/smol-rs/smol)). Rewrite the test above like this:
35 |
36 | ```rust
37 | #[tokio::test]
38 | async fn test_transfer() {
39 | ...
40 | }
41 | ```
42 |
43 | :::note
44 | If you are using another attribute on top of the standard `#[test]`, make sure it plays nicely with the async runtime of your choosing. For example, if you are using [`test-env-log`](https://crates.io/crates/test-env-log) and `tokio`, then you need to mark your tests with `#[test_env_log::test(tokio::test)]`.
45 | :::
46 |
47 | The second change is that `workspaces-rs` makes an extensive use of [`anyhow::Result`](https://docs.rs/anyhow/latest/anyhow/type.Result.html). Although you can work with `Result` directly, our recommendation is to make your tests return `anyhow::Result<()>` like this:
48 |
49 | ```rust
50 | #[tokio::test]
51 | async fn test_transfer() -> anyhow::Result<()> {
52 | ...
53 | }
54 | ```
55 |
56 | This way you can use `?` anywhere inside the test to safely unpack any `anyhow::Result` type to `R` (will be very useful further down the guide). Note that the test will fail if `anyhow::Result` cannot be unpacked.
57 |
58 | ## Initialization and Deploying Contracts
59 |
60 | Unlike NEAR Simulator, `workspaces-rs` uses an actual NEAR node and makes all calls through it. First, you need to decide which network you want your tests to be run on:
61 |
62 | - `sandbox` - perfect choice if you are just interested in local development and testing; `workspaces-rs` will instantiate a [sandbox](https://github.com/near/sandbox) instance on your local machine which will run an isolated NEAR node.
63 | - `testnet` - an environment much closer to the real world; you can test integrations with other deployed contracts on testnet without bearing any financial risk.
64 | - `mainnet` - a network with reduced amount of features due to how dangerous it can be to do transactions there, but can still be useful for automating deployments and pulling deployed contracts.
65 |
66 | In this guide we will be focusing on `sandbox` since it covers the same use cases NEAR Simulator did. But of course feel free to explore whether other networks can be of potential use to you when writing new tests/workflows.
67 |
68 | One of the ways to initialize simulator and deploy a contract is shown below (the other way is through `deploy!` macro which we will look at in the next section):
69 |
70 | ```rust title="Deployment - near-sdk-sim"
71 | use near_sdk_sim::{init_simulator, to_yocto};
72 |
73 | near_sdk_sim::lazy_static_include::lazy_static_include_bytes! {
74 | WASM_BYTES => "res/contract.wasm",
75 | }
76 |
77 | const ID: &str = "contract-id";
78 |
79 | ...
80 |
81 | let root = init_simulator(...);
82 | let contract = root.deploy(&WASM_BYTES, ID.parse().unwrap(), to_yocto("5"));
83 | ```
84 |
85 | Although `workspaces-rs` provides a way to specify the account id for a contract to be deployed, usually it does not matter in the context of a single test. If you are fine with generating a random developer account and initializing it with 100N, then you can replace the snippet above with this:
86 |
87 | ```rust title="Deployment - workspaces-rs"
88 | let worker = workspaces::sandbox().await?;
89 | let contract = worker.dev_deploy(include_bytes!("../res/contract.wasm")).await?;
90 | ```
91 |
92 | Alternatively, use this if you care about the account id:
93 |
94 | ```rust title="Deployment - workspaces-rs (with explicit account id)"
95 | let worker = workspaces::sandbox().await?;
96 | let (_, sk) = worker.dev_generate().await;
97 | let id: AccountId = "contract-id".parse()?;
98 | let contract = worker
99 | .create_tla_and_deploy(
100 | id,
101 | sk,
102 | include_bytes!("../examples/res/non_fungible_token.wasm"),
103 | )
104 | .await?
105 | .result;
106 | ```
107 |
108 | :::danger
109 | 'dev_deploy' can't supply the initial balance since testnet controls this amount in the helper contract which is what we're using to create dev accounts on testnet. So, to make it simple, we don't supply it at all (sandbox included). It is however possible to create a **subaccount** with a certain balance in sandbox, they can grab the root account and do:
110 |
111 | ```rust title="Deployment - workspaces-rs (with initial balance)"
112 | let root = worker.root_acount();
113 | root.create_subaccount(...)
114 | .initial_balance(...)
115 | ...
116 | ```
117 |
118 | :::
119 |
120 | :::caution
121 | You might have noticed that `init_simulator` used to accept an optional genesis config. Unfortunately, `workspaces-rs` does not support this feature yet, but we are trying to understand the need for this and properly design it. Please feel free to share your use case [here](https://github.com/near/workspaces-rs/issues/68).
122 | :::
123 |
124 | ## Making Transactions and View Calls
125 |
126 | As always, let's take a look at how we used to make calls with NEAR Simulator:
127 |
128 | ```rust title="Calls - near-sdk-sim"
129 | // Example 1: No Macros
130 | root.call(
131 | ft.account_id(),
132 | "ft_transfer",
133 | &json!({
134 | "receiver_id": alice.account_id(),
135 | "amount": U128::from(transfer_amount)
136 | })
137 | .to_string()
138 | .into_bytes(),
139 | 300_000_000_000_000,
140 | 1,
141 | );
142 |
143 | let root_balance: U128 = root.view(
144 | ft.account_id(),
145 | "ft_balance_of",
146 | &json!({
147 | "account_id": root.account_id()
148 | })
149 | .to_string()
150 | .into_bytes(),
151 | )
152 | .unwrap_json();
153 |
154 | // Example 2: With Macros
155 | call!(
156 | root,
157 | ft.ft_transfer(alice.account_id(), transfer_amount.into(), None),
158 | deposit = 1
159 | gas = 300_000_000_000_000
160 | );
161 |
162 | let root_balance: U128 = view!(ft.ft_balance_of(root.account_id())).unwrap_json();
163 | ```
164 |
165 | Note how Example 2's `call!` and `view!` macros accept a contract function invocation as if it was just regular Rust. Unlike NEAR Simulator, `workspaces-rs` never stores metadata about the deployed contract and hence does not support high-level syntax like that. This might change in the future once our ACI implementation is ready, but for the remainder of this section we will be migrating Example 1.
166 |
167 | Workspaces have a unified way of making all types of calls via a [builder](https://doc.rust-lang.org/1.0.0/style/ownership/builders.html) pattern. Generally, calls are constructed by following these steps:
168 |
169 | 1. Create a `CallBuilder` by invoking `Contract::call`
170 | 2. Pass function call arguments via `CallBuilder::args_json` or `CallBuilder::args_borsh` depending on which serialization algorithm your contract is using
171 | 3. Configure gas and deposit (if needed) via `CallBuilder::gas` and `CallBuilder::deposit`
172 | 4. Finalize the call by consuming builder via `CallBuilder::transaction` or `CallBuilder::view` depending on what kind of call you want to make
173 |
174 | Reference this migration of Example 1 for migrating your own calls:
175 |
176 | ```rust title="Calls - workspaces-rs"
177 | contract
178 | .call(&worker, "ft_transfer")
179 | .args_json((alice.id(), transfer_amount, Option::::None))?
180 | .gas(300_000_000_000_000)
181 | .deposit(ONE_YOCTO)
182 | .transact()
183 | .await?;
184 |
185 | let root_balance: U128 = contract
186 | .call(&worker, "ft_balance_of")
187 | .args_json((contract.id(),))?
188 | .view()
189 | .await?
190 | .json()?;
191 | ```
192 |
193 | :::note
194 | Note that you have to pass arguments as any serializable type representing a sequential list. Tuples are usually the best candidate due to their heterogeneous nature (remember that you can construct a unary tuple by placing a comma before the closing bracket like this: `(el,)`). Passing in an object formatted with the `json!()` macro is also supported.
195 | :::
196 |
197 | ### Batch Transactions
198 |
199 | There is a special builder for making batch transactions that can be instantiated by calling `Contract::batch`. Consider the following snippet making a batch transaction consisting of two calls:
200 |
201 | ```rust title="Batch Transaction - near-sdk-sim"
202 | let res = root
203 | .create_transaction(contract.account_id())
204 | .function_call(
205 | "ft_transfer_call".to_string(),
206 | json!({
207 | "receiver_id": defi_contract.account_id(),
208 | "amount": transfer_amount.to_string(),
209 | "msg": "10",
210 | })
211 | .to_string()
212 | .into_bytes(),
213 | 300_000_000_000_000 / 2,
214 | 1,
215 | )
216 | .function_call(
217 | "storage_unregister".to_string(),
218 | json!({
219 | "force": true
220 | })
221 | .to_string()
222 | .into_bytes(),
223 | 300_000_000_000_000 / 2,
224 | 1,
225 | )
226 | .submit();
227 | ```
228 |
229 | There are no caveats here, the snippet can be straightforwardly mapped into the following:
230 |
231 | ```rust title="Batch Transaction - workspace-rs"
232 | let res = contract
233 | .batch(&worker)
234 | .call(
235 | Function::new("ft_transfer_call")
236 | .args_json((defi_contract.id(), transfer_amount, Option::::None, "10"))?
237 | .gas(300_000_000_000_000 / 2)
238 | .deposit(1),
239 | )
240 | .call(
241 | Function::new("storage_unregister")
242 | .args_json((Some(true),))?
243 | .gas(300_000_000_000_000 / 2)
244 | .deposit(1),
245 | )
246 | .transact()
247 | .await?;
248 | ```
249 |
250 | ## Inspecting Logs
251 |
252 | The API for inspecting logs is fairly close to what it was in NEAR Simulator, but there are still some things you should keep in mind when migrating. Let's take the same transaction we used in the [batch transactions](#batch-transactions) section and try to inspect its logs. This is how one would check that the transaction logged a specific message in a certain position with NEAR Simulator:
253 |
254 | ```rust title="Logs - near-sdk-sim"
255 | assert_eq!(
256 | res.logs()[1],
257 | format!("Closed @{} with {}", contract.account_id(), initial_balance - transfer_amount)
258 | );
259 | ```
260 |
261 | The `workspaces-rs` counterpart might seem almost identical at the first look:
262 |
263 | ```rust title="Logs - workspaces-rs"
264 | assert_eq!(
265 | res.logs()[1],
266 | format!("Closed @{} with {}", contract.id(), initial_balance.0 - transfer_amount.0)
267 | );
268 | ```
269 |
270 | However, it can actually behave differently depending on your use case, because while near-sdk-sim version only returns the logs from the transaction, the workspaces version returns all logs from both the transaction and receipt outcomes. If you want a literal counterpart, please use `res.outcome().logs`.
271 |
272 | Another common use case is examining receipt outcome logs like this:
273 |
274 | ```rust title="Logs - nead-sdk-sim"
275 | let outcome = res.get_receipt_results().remove(5).unwrap();
276 |
277 | assert_eq!(outcome.logs()[0], "The account of the sender was deleted");
278 | assert_eq!(
279 | outcome.logs()[2],
280 | format!("Account @{} burned {}", root.account_id(), 10)
281 | );
282 | ```
283 |
284 | Which is straightforwardly replaced with:
285 |
286 | ```rust title="Logs - workspaces-rs"
287 | let outcome = &res.receipt_outcomes()[5];
288 | assert_eq!(outcome.logs[0], "The account of the sender was deleted");
289 | assert_eq!(outcome.logs[2], format!("Account @{} burned {}", contract.id(), 10));
290 | ```
291 |
292 | ## Profiling Gas
293 |
294 | NEAR Simulator never had accurate gas estimations since it only tried to mirror nearcore, but nearcore has extra functionality on top which consumes gas (like cross-contract calls are processed separately from the same transaction and that incurs gas fees). Workspaces offers the better experience here and aligns very well with what you can do on testnet and mainnet. It provides the added benefit of allowing the developer to accurately profile gas usage before deploying to `mainnet`.
295 |
296 | :::warning
297 | Since `workspaces-rs` is now using accurate gas measurements, some testing flows that were previously being tested with sdk-sim that would depend on gas reports might not work anymore. You should do your due diligence if you plan to deploy to `mainnet`.
298 | :::
299 |
300 | Let's once again return to the [batch transactions](#batch-transactions) example and see how we would estimate gas burnt by a given transaction:
301 |
302 | ```rust title="Gas (transaction) - near-sdk-sim"
303 | println!("Burnt gas (transaction): {}", res.gas_burnt());
304 | ```
305 |
306 | Just like with [inspecting logs](#inspecting-logs), one might mistakenly think that
307 |
308 | ```rust title="Gas (all) - workspaces-rs"
309 | println!("Burnt gas (all): {}", res.total_gas_burnt);
310 | ```
311 |
312 | is the corresponding `workspaces-rs` snippet, but `CallExecutionDetails::total_gas_burnt` includes all gas burnt by call execution, including by receipts. This is exposed as a surface level API since it is a much more commonly used concept, but if you do actually want gas burnt by transaction itself you can do it like this:
313 |
314 | ```rust title="Gas (transaction) - workspaces-rs"
315 | println!("Burnt gas (transaction): {}", res.outcome().gas_burnt);
316 | ```
317 |
--------------------------------------------------------------------------------