├── static ├── .nojekyll ├── img │ ├── favicon.ico │ ├── near_logo.ico │ ├── near_protocol_logo.png │ ├── near_logo.svg │ └── near_logo_white.svg └── index.html ├── .github ├── CODEOWNERS ├── workflows │ ├── links.yml │ ├── build.yml │ └── spellcheck.yml └── ISSUE_TEMPLATE │ └── BOUNTY.yml ├── docs ├── promises │ ├── _category_.json │ ├── intro.md │ ├── create-account.md │ ├── token-tx.md │ └── deploy-contract.md ├── testing │ ├── _category_.json │ ├── unit-tests.md │ ├── integration-tests.md │ └── workspaces-migration-guide.md ├── building │ ├── _category_.json │ ├── reproducible-builds.md │ ├── post-processing-tools.md │ └── basic-build.md ├── upgrading │ ├── _category_.json │ ├── via-dao-vote.md │ ├── prototyping.md │ └── production-basics.md ├── contract-interface │ ├── _category_.json │ ├── payable-methods.md │ ├── private-methods.md │ ├── public-methods.md │ ├── contract-mutability.md │ └── serialization-interface.md ├── cross-contract │ ├── _category_.json │ └── callbacks.md ├── contract-structure │ ├── _category_.json │ ├── near-bindgen.md │ ├── nesting.md │ └── collections.md ├── reducing-contract-size │ ├── _category_.json │ └── examples.md ├── intro.md └── best-practices.md ├── tsconfig.json ├── babel.config.js ├── src ├── theme │ ├── Footer │ │ ├── styles.module.css │ │ └── index.tsx │ └── Root.js └── css │ └── custom.css ├── .gitignore ├── sidebars.js ├── README.md ├── mlc_config.json ├── package.json └── docusaurus.config.js /static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @austinabell @ChaoticTempest 2 | -------------------------------------------------------------------------------- /docs/promises/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Promises", 3 | "position": 4 4 | } -------------------------------------------------------------------------------- /docs/testing/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Testing", 3 | "position": 6 4 | } 5 | -------------------------------------------------------------------------------- /static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near/sdk-docs/HEAD/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/building/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Building Contracts", 3 | "position": 7 4 | } 5 | -------------------------------------------------------------------------------- /static/img/near_logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near/sdk-docs/HEAD/static/img/near_logo.ico -------------------------------------------------------------------------------- /docs/upgrading/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Upgrading Contracts", 3 | "position": 8 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/docusaurus/tsconfig.json", 3 | "include": ["src/"] 4 | } -------------------------------------------------------------------------------- /docs/contract-interface/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Contract Interface", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /docs/cross-contract/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Cross-Contract Calls", 3 | "position": 3 4 | } 5 | -------------------------------------------------------------------------------- /docs/contract-structure/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Structure of a Contract", 3 | "position": 2 4 | } 5 | -------------------------------------------------------------------------------- /docs/reducing-contract-size/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Reducing Contract Size", 3 | "position": 10 4 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /static/img/near_protocol_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near/sdk-docs/HEAD/static/img/near_protocol_logo.png -------------------------------------------------------------------------------- /src/theme/Footer/styles.module.css: -------------------------------------------------------------------------------- 1 | .footerLogoLink { 2 | opacity: 0.5; 3 | transition: opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default); 4 | } 5 | 6 | .footerLogoLink:hover { 7 | opacity: 1; 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /.github/workflows/links.yml: -------------------------------------------------------------------------------- 1 | name: Check Markdown links 2 | 3 | on: push 4 | 5 | jobs: 6 | markdown-link-check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@master 10 | - uses: gaurav-nelson/github-action-markdown-link-check@v1 11 | with: 12 | use-quiet-mode: 'yes' 13 | config-file: 'mlc_config.json' 14 | folder-path: 'docs' 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build-check: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-node@v2 9 | with: 10 | node-version: '16' 11 | - name: Build Docusaurus docs 12 | run: | 13 | yarn 14 | yarn build 15 | env: 16 | CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} 17 | -------------------------------------------------------------------------------- /.github/workflows/spellcheck.yml: -------------------------------------------------------------------------------- 1 | name: spellchecker 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | misspell: 10 | name: runner / misspell 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out code. 14 | uses: actions/checkout@v1 15 | - name: misspell 16 | id: check_for_typos 17 | uses: reviewdog/action-misspell@v1 18 | with: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | path: "./docs" 21 | locale: "US" 22 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 |
61 |
62 | {links && links.length > 0 && ( 63 |
64 | {links.map((linkItem, i) => ( 65 |
66 | {linkItem.title != null ? ( 67 |

{linkItem.title}

68 | ) : null} 69 | {linkItem.items != null && 70 | Array.isArray(linkItem.items) && 71 | linkItem.items.length > 0 ? ( 72 |
    73 | {linkItem.items.map((item, key) => 74 | item.html ? ( 75 |
  • 84 | ) : ( 85 |
  • 86 | 87 |
  • 88 | ), 89 | )} 90 |
91 | ) : null} 92 |
93 | ))} 94 |
95 | )} 96 | {(logo || copyright) && ( 97 |
98 | {logo && (logo.src || logo.srcDark) && ( 99 |
100 | {logo.href ? ( 101 | 102 | 103 | 104 | ) : ( 105 | 106 | )} 107 |
108 | )} 109 | {copyright ? ( 110 |
118 | ) : null} 119 |
120 | )} 121 |
122 |
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 | --------------------------------------------------------------------------------