From d3c7777d6b70f0f67a4f806142f76059ee0038ed Mon Sep 17 00:00:00 2001 From: Damian Parrino Date: Tue, 3 May 2022 11:29:40 -0300 Subject: [PATCH 1/6] workspaces --- docs/develop/contracts/workspaces.md | 32 ++ .../contracts/workspaces/workspaces-js.md | 289 ++++++++++++++++++ .../contracts/workspaces/workspaces-rs.md | 276 +++++++++++++++++ website/sidebars.json | 9 + 4 files changed, 606 insertions(+) create mode 100644 docs/develop/contracts/workspaces.md create mode 100644 docs/tutorials/contracts/workspaces/workspaces-js.md create mode 100644 docs/tutorials/contracts/workspaces/workspaces-rs.md diff --git a/docs/develop/contracts/workspaces.md b/docs/develop/contracts/workspaces.md new file mode 100644 index 00000000000..06ae1cd1b99 --- /dev/null +++ b/docs/develop/contracts/workspaces.md @@ -0,0 +1,32 @@ +--- +id: workspaces +title: NEAR Workspaces +sidebar_label: NEAR Workspaces +--- + +NEAR Workspaces is a library for automating workflows and writing tests for NEAR smart contracts. +You can use it as is or integrate with test runner of your choise (AVA, Jest, Mocha, etc.). If you don't have a preference, we suggest you to use AVA. + +## Overview + +Controlled, concurrent workspaces in local NEAR Sandbox blockchains or on NEAR TestNet. Fun, deterministic testing and powerful scripting for NEAR. + + + +Write tests once, run them both on NEAR TestNet and a controlled NEAR Sandbox local environment + +## Libraries + +The same Workspaces interface is supported by libraries for the following languages: + +- TypeScript/JavaScript: `workspaces-js` +- Rust: `workspaces-rs` + +| Language | Link | +|----------|------| +| JavaScript | https://github.com/near/workspaces-js | +| Rust | https://github.com/near/workspaces-rs | + + +## Tutorials + diff --git a/docs/tutorials/contracts/workspaces/workspaces-js.md b/docs/tutorials/contracts/workspaces/workspaces-js.md new file mode 100644 index 00000000000..dc5526a1ae6 --- /dev/null +++ b/docs/tutorials/contracts/workspaces/workspaces-js.md @@ -0,0 +1,289 @@ +--- +id: workspaces-js +title: NEAR Workspaces (TypeScript/JavaScript Edition) +sidebar_label: Workspaces JS +--- + + +`NEAR Workspaces` is a library for automating workflows and writing tests for NEAR smart contracts. You can use it as is or integrate with test runner of your choise (AVA, Jest, Mocha, etc.). If you don't have a preference, we suggest you to use AVA. + +## Quick Start (without testing frameworks) + +To get started with NEAR Workspaces you need to do only two things: + +1. Initialize a `Worker`. + + ```ts + const worker = await Worker.init(); + const root = worker.rootAccount; + + const alice = await root.createSubAccount('alice'); + const contract = await root.createAndDeploy( + root.getSubAccount('contract-name').accountId, + 'path/to/compiled.wasm' + ); + ``` + + Let's step through this. + + 1. `Worker.init` initializes a new `SandboxWorker` or `TestnetWorker` depending on the config. `SandboxWorker` contains [NEAR Sandbox](https://docs.near.org/docs/develop/contracts/sandbox), which is essentially a local mini-NEAR blockchain. You can create one `Worker` per test to get its own data directory and port (for Sandbox) or root account (for Testnet), so that tests can run in parallel without race conditions in accessing states. If there's no state intervention. you can also reuse the `Worker` to speedup the tests. + 2. The worker has a `root` account. For `SandboxWorker`, it's `test.near`. For `TestnetWorker`, it creates a unique account. The following accounts are created as subaccounts of the root account. The name of the account will change from different runs, so you should not refer to them by hard coded account name. You can access them via the account object, such as `root`, `alice` and `contract` above. + 3. `root.createSubAccount` creates a new subaccount of `root` with the given name, for example `alice.`. + 4. `root.createAndDeploy` creates an account with the given name, `contract-name.`, then deploys the specified Wasm file to it. + 5. `path/to/compiled.wasm` will resolve relative to your project root. That is, the nearest directory with a `package.json` file, or your current working directory if no `package.json` is found. To construct a path relative to your test file, you can use `path.join(__dirname, '../etc/etc.wasm')` ([more info](https://nodejs.org/api/path.html#path_path_join_paths)). + 6. `worker` contains a reference to this data directory, so that multiple tests can use it as a starting point. + 7. If you're using a test framework, you can save the `worker` object and account objects `root`, `alice`, `contract` to test context to reuse them in subsequent tests. + 8. At the end of test, call `await worker.tearDown()` to shuts down the Worker. It gracefully shuts down the Sandbox instance it ran in the background. However, it keeps the data directory around. That's what stores the state of the two accounts that were created (`alice` and `contract-account-name` with its deployed contract). + +2. Writing tests. + + `near-workspaces` is designed for concurrency. Here's a simple way to get concurrent runs using plain JS: + + ```ts + import {strict as assert} from 'assert'; + + await Promise.all([ + async () => { + await alice.call( + contract, + 'some_update_function', + {some_string_argument: 'cool', some_number_argument: 42} + ); + const result = await contract.view( + 'some_view_function', + {account_id: alice} + ); + assert.equal(result, 'whatever'); + }, + async () => { + const result = await contract.view( + 'some_view_function', + {account_id: alice} + ); + /* Note that we expect the value returned from `some_view_function` to be + a default here, because this `fork` runs *at the same time* as the + previous, in a separate local blockchain */ + assert.equal(result, 'some default'); + } + ]); + ``` + + Let's step through this. + + 1. `worker` and accounts such as `alice` are created before. + 2. `call` syntax mirrors [near-cli](https://github.com/near/near-cli) and either returns the successful return value of the given function or throws the encountered error. If you want to inspect a full transaction and/or avoid the `throw` behavior, you can use `callRaw` instead. + 3. While `call` is invoked on the account _doing the call_ (`alice.call(contract, …)`), `view` is invoked on the account _being viewed_ (`contract.view(…)`). This is because the caller of a view is irrelevant and ignored. + 4. Gotcha: the full account names does not match the strings passed to `createSubAccount` and `createAndDeploy`, which is why you must write `alice.call(contract, …)` rather than `alice.call('contract-account-name', …)`. But! The `Account` class overrides `toJSON` so that you can pass `{account_id: alice}` in arguments rather than `{account_id: alice.accountId}`. If you need the generated account ID in some other circumstance, remember to use `alice.accountId`. + + +See the [tests](https://github.com/near/workspaces-js/tree/main/__tests__) directory in this project for more examples. + +## Quick Start with AVA + +Since `near-workspaces` is designed for concurrency, AVA is a great fit, because it runs tests concurrently by default. To use`NEAR Workspaces` with AVA: + 1. Start with the basic setup described [here](https://github.com/avajs/ava). + 2. Add custom script for running tests on Testnet (if needed). Check instructions in `Running on Testnet` section. + 3. Add your tests following these example: + + ```ts + import {Worker} from 'near-workspaces'; + import anyTest, {TestFn} from 'ava' + + const test = anyTest as TestFn<{ + worker: Worker; + accounts: Record; + }>; + + /* If using `test.before`, each test is reusing the same worker; + If you'd like to make a copy of the worker, use `beforeEach` after `afterEach`, + which allows you to isolate the state for each test */ + test.before(async t => { + const worker = await Worker.init(); + const root = worker.rootAccount; + const contract = await root.createAndDeploy( + 'account-id-for-contract', + 'path/to/contract/file.wasm', + ); + /* Account that you will be able to use in your tests */ + const ali = await root.createSubAccount('ali'); + t.context.worker = worker; + t.context.accounts = {root, contract, ali}; + }) + + test('Test name', async t => { + const {ali, contract} = t.context.accounts; + await ali.call(contract, 'set_status', {message: 'hello'}); + const result: string = await contract.view('get_status', {account_id: ali}); + t.is(result, 'hello'); + }); + + test.after(async t => { + // Stop Sandbox server + await t.context.worker.tearDown().catch(error => { + console.log('Failed to stop the Sandbox:', error); + }); + }); + ``` + + +## "Spooning" Contracts from Testnet and Mainnet + +[Spooning a blockchain](https://coinmarketcap.com/alexandria/glossary/spoon-blockchain) is copying the data from one network into a different network. near-workspaces makes it easy to copy data from Mainnet or Testnet contracts into your local Sandbox environment: + +```ts +const refFinance = await root.importContract({ + mainnetContract: 'v2.ref-finance.near', + blockId: 50_000_000, + withData: true, +}); +``` + +This would copy the Wasm bytes and contract state from [v2.ref-finance.near](https://explorer.near.org/accounts/v2.ref-finance.near) to your local blockchain as it existed at block `50_000_000`. This makes use of Sandbox's special [patch state](#patch-state-on-the-fly) feature to keep the contract name the same, even though the top level account might not exist locally (note that this means it only works in Sandbox testing mode). You can then interact with the contract in a deterministic way the same way you interact with all other accounts created with near-workspaces. + +Gotcha: `withData` will only work out-of-the-box if the contract's data is 50kB or less. This is due to the default configuration of RPC servers; see [the "Heads Up" note here](https://docs.near.org/docs/api/rpc/contracts#view-contract-state). Some teams at NEAR are hard at work giving you an easy way to run your own RPC server, at which point you can point tests at your custom RPC endpoint and get around the 50kB limit. + +See an [example of spooning](https://github.com/near/workspaces-js/blob/main/__tests__/05.spoon-contract-to-sandbox.ava.ts) contracts. + +## Running on Testnet + +near-workspaces is set up so that you can write tests once and run them against a local Sandbox node (the default behavior) or against [NEAR TestNet](https://docs.near.org/docs/concepts/networks). Some reasons this might be helpful: + +* Gives higher confidence that your contracts work as expected +* You can test against deployed testnet contracts +* If something seems off in Sandbox mode, you can compare it to testnet + +In order to use Workspaces JS in testnet mode you will need to have a testnet account. You can create one [here](https://wallet.testnet.near.org/). + +You can switch to testnet mode in three ways. + +1. When creating Worker set network to `testnet` and pass your master account: + + ```ts + const worker = await Worker.init({ + network: 'testnet', + testnetMasterAccountId: '', + }) + ``` + +2. Set the `NEAR_WORKSPACES_NETWORK` and `TESTNET_MASTER_ACCOUNT_ID` environment variables when running your tests: + + ```bash + NEAR_WORKSPACES_NETWORK=testnet TESTNET_MASTER_ACCOUNT_ID= node test.js + ``` + + If you set this environment variables and pass `{network: 'testnet', testnetMasterAccountId: }` to `Worker.init`, the config object takes precedence. + +3. If using `near-workspaces` with AVA, you can use a custom config file. Other test runners allow similar config files; adjust the following instructions for your situation. + + Create a file in the same directory as your `package.json` called `ava.testnet.config.cjs` with the following contents: + + ```js + module.exports = { + ...require('near-workspaces/ava.testnet.config.cjs'), + ...require('./ava.config.cjs'), + }; + module.exports.environmentVariables = { + TESTNET_MASTER_ACCOUNT_ID: '', + }; + ``` + + The [near-workspaces/ava.testnet.config.cjs](https://github.com/near/workspaces-js/blob/main/ava.testnet.config.cjs) import sets the `NEAR_WORKSPACES_NETWORK` environment variable for you. A benefit of this approach is that you can then easily ignore files that should only run in Sandbox mode. + + Now you'll also want to add a `test:testnet` script to your `package.json`'s `scripts` section: + + ```diff + "scripts": { + "test": "ava", + + "test:testnet": "ava --config ./ava.testnet.config.cjs" + } + ``` + + +### Stepping through a testnet example + +Let's revisit a shortened version of the example from How It Works above, describing what will happen in Testnet. + +1. Create a `Worker`. + + ```ts + const worker = await Worker.init(); + ``` + + `Worker.init` creates a unique testnet account as root account. + +2. Write tests. + + ```ts + await Promise.all([ + async () => { + await alice.call( + contract, + 'some_update_function', + {some_string_argument: 'cool', some_number_argument: 42} + ); + const result = await contract.view( + 'some_view_function', + {account_id: alice} + ); + assert.equal(result, 'whatever'); + }, + async () => { + const result = await contract.view( + 'some_view_function', + {account_id: alice} + ); + assert.equal(result, 'some default'); + } + ]); + ``` + +Note: Sometimes account creation rate limits are reached on testnet, simply wait a little while and try again. + +### Running tests only in Sandbox + +If some of your runs take advantage of Sandbox-specific features, you can skip these on testnet in two ways: + +1. You can skip entire sections of your files by checking `getNetworkFromEnv() === 'sandbox'`. + + ```ts + let worker = Worker.init(); + // things make sense to any network + const root = worker.rootAccount; + const alice = await root.createSubAccount('alice'); + + if (getNetworkFromEnv() === 'sandbox') { + // thing that only makes sense with sandbox + } + ``` + +2. Use a separate testnet config file, as described under the "Running on Testnet" heading above. Specify test files to include and exclude in config file. + +## Patch State on the Fly + +In Sandbox-mode, you can add or modify any contract state, contract code, account or access key with `patchState`. + +You cannot perform arbitrary mutation on contract state with transactions since transactions can only include contract calls that mutate state in a contract-programmed way. For example, with an NFT contract, you can perform some operation with NFTs you have ownership of, but you cannot manipulate NFTs that are owned by other accounts since the smart contract is coded with checks to reject that. This is the expected behavior of the NFT contract. However, you may want to change another person's NFT for a test setup. This is called "arbitrary mutation on contract state" and can be done with `patchState`. Alternatively you can stop the node, dump state at genesis, edit genesis, and restart the node. The later approach is more complicated to do and also cannot be performed without restarting the node. + +It is true that you can alter contract code, accounts, and access keys using normal transactions via the `DeployContract`, `CreateAccount`, and `AddKey` [actions](https://nomicon.io/RuntimeSpec/Actions.html?highlight=actions#actions). But this limits you to altering your own account or sub-account. `patchState` allows you to perform these operations on any account. + +To see an example of how to do this, see the [patch-state test](https://github.com/near/workspaces-js/blob/main/__tests__/02.patch-state.ava.ts). + +## Pro Tips + +* `NEAR_WORKSPACES_DEBUG=true` – run tests with this environment variable set to get copious debug output and a full log file for each Sandbox instance. + +* `Worker.init` [config](https://github.com/near/workspaces-js/blob/main/packages/js/src/interfaces.ts) – you can pass a config object as the first argument to `Worker.init`. This lets you do things like: + + * skip initialization if specified data directory already exists (the default behavior) + + ```ts + Worker.init( + { rm: false, homeDir: './test-data/alice-owns-an-nft' }, + ) + ``` + + * always recreate such data directory instead with `rm: true` + + * specify which port to run on + + * and more! diff --git a/docs/tutorials/contracts/workspaces/workspaces-rs.md b/docs/tutorials/contracts/workspaces/workspaces-rs.md new file mode 100644 index 00000000000..fca23c5cb97 --- /dev/null +++ b/docs/tutorials/contracts/workspaces/workspaces-rs.md @@ -0,0 +1,276 @@ +--- +id: workspaces-rs +title: NEAR Workspaces (Rust Edition) +sidebar_label: Workspaces Rust +--- + +Rust library for automating workflows and writing tests for NEAR smart contracts. This software is not final, and will likely change. + +## Release notes + +**Release notes and unreleased changes can be found in the [CHANGELOG](CHANGELOG.md)** + +## Requirements + +- Rust v1.56 and up +- MacOS (x86) or Linux (x86) for sandbox tests. Testnet is available regardless + +### M1 MacOS + +NOTE: Current version of `workspaces-rs` does not support use on M1 chip devices due to internal upgrades with wasmer. M1 users should use `workspaces-rs` version `0.1.1` until this problem gets resolved. Check the progress on this issue [here](https://github.com/near/workspaces-rs/issues/110). + +Even with the above note, we can use `workspaces-rs` with version `0.1.1` on M1 by setting up rosetta plus our cross compile target: +``` +softwareupdate --install-rosetta +rustup default stable-x86_64-apple-darwin +``` + +### WASM compilation not supported + +`workspaces-rs`, the library itself, does not currently compile to WASM. Best to put this dependency in `[dev-dependencies]` section of `Cargo.toml` if we were trying to run this library alongside something that already does compile to WASM, such as `near-sdk-rs`. + +## Simple Testing Case + +A simple test to get us going and familiar with `workspaces` framework. Here, we will be going through the NFT contract and how we can test it with `workspaces-rs`. + +### Setup -- Imports +First, we need to declare some imports for convenience. + +```rust +// macro allowing us to convert human readable units to workspace units. +use near_units::parse_near; + +// macro allowing us to convert args into JSON bytes to be read by the contract. +use serde_json::json; + +// Additional convenient imports that allows workspaces to function readily. +use workspaces::prelude::*; +``` + +We will need to have our pre-compiled WASM contract ahead of time and know its path. In this showcase, we will be pointing to the example's NFT contract: + +```rust +const NFT_WASM_FILEPATH: &str = "./examples/res/non_fungible_token.wasm"; +``` + +NOTE: there is an unstable feature that will allow us to compile our projects during testing time as well. Take a look at the feature section [Compiling Contracts During Test Time](#compiling-contracts-during-test-time) + +### Setup -- Setting up Sandbox and Deploying NFT Contract + +This includes launching our sandbox, loading our wasm file and deploying that wasm file to the sandbox environment. + +```rust + +#[tokio::test] +async fn test_nft_contract() -> anyhow::Result<()> { + let worker = workspaces::sandbox().await?; + let wasm = std::fs::read(NFT_WASM_FILEPATH)?; + let contract = worker.dev_deploy(&wasm).await?; +``` +Where +* `anyhow` - A crate that deals with error handling, making it more robust for developers. +* `worker` - Our gateway towards interacting with our sandbox environment. +* `contract`- The deployed contract on sandbox the developer interacts with. + +Then we'll go directly into making a call into the contract, and initialize the NFT contract's metadata: +```rust + let outcome = contract + .call(&worker, "new_default_meta") + .args_json(json!({ + "owner_id": contract.id(), + }))? + .transact() + .await?; + + // outcome contains data like logs, receipts and transaction outcomes. + println!("new_default_meta outcome: {:#?}", outcome); +``` + +Afterwards, let's mint an NFT via `nft_mint`. This showcases some extra arguments we can supply, such as deposit and gas: + +```rust + let deposit = 10000000000000000000000; + let outcome = contract + .call(&worker, "nft_mint") + .args_json(json!({ + "token_id": "0", + "token_owner_id": contract.id(), + "token_metadata": { + "title": "Olympus Mons", + "dscription": "Tallest mountain in charted solar system", + "copies": 1, + }, + }))? + .deposit(deposit) + // nft_mint might consume more than default gas, so supply our own gas value: + .gas(near_units::parse_gas("300 T")) + .transact() + .await?; + + println!("nft_mint outcome: {:#?}", outcome); +``` +Then later on, we can view our minted NFT's metadata via our `view` call into `nft_metadata`: +```rust + let result: serde_json::Value = contract + .call(&worker, "nft_metadata") + .view() + .await? + .json()?; + + println!("--------------\n{}", result); + println!("Dev Account ID: {}", contract.id()); + Ok(()) +} +``` + +## Examples + +More standalone examples can be found in `examples/src/*.rs`. + +To run the above NFT example, execute: +``` +cargo run --example nft +``` + +## Features + +### Choosing a network + +```rust +#[tokio::main] // or whatever runtime we want +async fn main() -> anyhow::Result<()> { + // Create a sandboxed environment. + // NOTE: Each call will create a new sandboxed environment + let worker = workspaces::sandbox().await?; + // or for testnet: + let worker = workspaces::testnet().await?; +} +``` + +### Helper Functions + +Need to make a helper function regardless of whatever Network? + +```rust +use workspaces::prelude::*; +use workspaces::{Contract, DevNetwork, Network, Worker}; + +// Helper function that calls into a contract we give it +async fn call_my_func(worker: Worker, contract: &Contract) -> anyhow::Result<()> { + // Call into the function `contract_function` with args: + contract.call(&worker, "contract_function") + .args_json(serde_json::json!({ + "message": msg, + })? + .transact() + .await?; + Ok(()) +} + +// Create a helper function that deploys a specific contract +// NOTE: `dev_deploy` is only available on `DevNetwork`s such sandbox and testnet. +async fn deploy_my_contract(worker: Worker) -> anyhow::Result { + worker.dev_deploy(&std::fs::read(CONTRACT_FILE)?).await +} +``` + +### Spooning - Pulling Existing State and Contracts from Mainnet/Testnet + +This example will showcase spooning state from a testnet contract into our local sandbox environment. + +We will first start with the usual imports: +```rust +use near_units::{parse_gas, parse_near}; +use workspaces::network::Sandbox; +use workspaces::prelude::*; +use workspaces::{Account, AccountId, BlockHeight, Contract, Worker}; +``` + +Then specify the contract name from testnet we want to be pulling: +```rust +const CONTRACT_ACCOUNT: &str = "contract_account_name_on_testnet.testnet"; +``` + +Let's also specify a specific block ID referencing back to a specific time. Just in case our contract or the one we're referencing has been changed or updated: + +```rust +const BLOCK_HEIGHT: BlockHeight = 12345; +``` + +Create a function called `pull_contract` which will pull the contract's `.wasm` file from the chain and deploy it onto our local sandbox. We'll have to re-initialize it with all the data to run tests. +```rust +async fn pull_contract(owner: &Account, worker: &Worker) -> anyhow::Result { + let testnet = workspaces::testnet_archival(); + let contract_id: AccountId = CONTRACT_ACCOUNT.parse()?; +``` + +This next line will actually pull down the relevant contract from testnet and set an initial balance on it with 1000 NEAR. + +Following that we will have to init the contract again with our own metadata. This is because the contract's data is to big for the RPC service to pull down, who's limits are set to 50mb. + +```rust + + let contract = worker + .import_contract(&contract_id, &testnet) + .initial_balance(parse_near!("1000 N")) + .block_height(BLOCK_HEIGHT) + .transact() + .await?; + + owner + .call(&worker, contract.id(), "init_method_name") + .args_json(serde_json::json!({ + "arg1": value1, + "arg2": value2, + }))? + .transact() + .await?; + + Ok(contract) +} +``` + +### Time Traveling + +`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: + +```rust +#[tokio::test] +async fn test_contract() -> anyhow::Result<()> { + let worker = workspaces::sandbox().await?; + let contract = worker.dev_deploy(WASM_BYTES); + + let blocks_to_advance = 10000; + worker.fast_forward(blocks_to_advance); + + // Now, "do_something_with_time" will be in the future and can act on future time-related state. + contract.call(&worker, "do_something_with_time") + .transact() + .await?; +} +``` + +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). + + +### Compiling Contracts During Test Time + +Note, this is an unstable feature and will very likely change. To enable it, add the `unstable` feature flag to `workspaces` dependency in `Cargo.toml`: + +```toml +[dependencies] +workspaces = { version = "...", features = ["unstable"] } +``` +Then, in our tests right before we call into `deploy` or `dev_deploy`, we can compile our projects: +```rust +#[tokio::test] +async fn test_contract() -> anyhow::Result<()> { + let wasm = workspaces::compile_project("path/to/contract-rs-project").await?; + + let worker = workspaces::sandbox().await?; + let contract = worker.dev_deploy(&wasm); + ... +} +``` + +For a full example, take a look at [workspaces/tests/deploy_project.rs](https://github.com/near/workspaces-rs/blob/main/workspaces/tests/deploy_project.rs). diff --git a/website/sidebars.json b/website/sidebars.json index 310f451ee9b..a725748790e 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -56,6 +56,7 @@ ], "Smart Contracts": [ "develop/contracts/overview", + "develop/contracts/workspaces", "develop/contracts/sandbox", "develop/contracts/debug-locally", { @@ -149,6 +150,14 @@ "tutorials/contracts/xcc-rust", "tutorials/contracts/cross-contract-calls" ] + }, + { + "type": "category", + "label": "Workspaces", + "items": [ + "tutorials/contracts/workspaces/workspaces-js", + "tutorials/contracts/workspaces/workspaces-rs" + ] } ], "Front-End": [ From 516a6844141fa16762b9cb09445059bf31e43a10 Mon Sep 17 00:00:00 2001 From: Damian Parrino Date: Thu, 5 May 2022 22:32:00 -0300 Subject: [PATCH 2/6] Update workspaces.md --- docs/develop/contracts/workspaces.md | 423 ++++++++++++++++++++++++++- 1 file changed, 415 insertions(+), 8 deletions(-) diff --git a/docs/develop/contracts/workspaces.md b/docs/develop/contracts/workspaces.md index 06ae1cd1b99..fe3ce7b7ac0 100644 --- a/docs/develop/contracts/workspaces.md +++ b/docs/develop/contracts/workspaces.md @@ -4,29 +4,436 @@ title: NEAR Workspaces sidebar_label: NEAR Workspaces --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + NEAR Workspaces is a library for automating workflows and writing tests for NEAR smart contracts. -You can use it as is or integrate with test runner of your choise (AVA, Jest, Mocha, etc.). If you don't have a preference, we suggest you to use AVA. +You can use it as-is or integrate it with a test runner of your choice (AVA, Jest, Mocha, etc.). +If you don't have a preference, we suggest you to use AVA. ## Overview Controlled, concurrent workspaces in local NEAR Sandbox blockchains or on NEAR TestNet. Fun, deterministic testing and powerful scripting for NEAR. - - Write tests once, run them both on NEAR TestNet and a controlled NEAR Sandbox local environment ## Libraries -The same Workspaces interface is supported by libraries for the following languages: - -- TypeScript/JavaScript: `workspaces-js` -- Rust: `workspaces-rs` +The same Workspaces interface is supported by the following libraries: | Language | Link | |----------|------| | JavaScript | https://github.com/near/workspaces-js | | Rust | https://github.com/near/workspaces-rs | +- TypeScript/JavaScript -## Tutorials + :::note + Current version of `workspaces-js` does not support the "Time Traveling" feature provided by the `fast_forward` method. This will be addressed in a future release. + ::: + +- Rust + + :::note + Current version of `workspaces-rs` does not support macOS on M1 chip devices due to internal upgrades with wasmer. M1 users should use `workspaces-rs` version `0.1.1` until this problem gets resolved. + ::: + + +## Quick Start + +To get started with NEAR Workspaces you need to do only two things: + +1. Initialize a `Worker`. +2. Write tests. + + + + +1. Initialing a `Worker` + + ```ts + const worker = await Worker.init(); + const root = worker.rootAccount; + + const alice = await root.createSubAccount('alice'); + const contract = await root.createAndDeploy( + root.getSubAccount('contract-name').accountId, + 'path/to/compiled.wasm' + ); + ``` + + Let's step through this. + + 1. `Worker.init` initializes a new `SandboxWorker` or `TestnetWorker` depending on the config. `SandboxWorker` contains [NEAR Sandbox](https://docs.near.org/docs/develop/contracts/sandbox), which is essentially a local mini-NEAR blockchain. You can create one `Worker` per test to get its own data directory and port (for Sandbox) or root account (for Testnet), so that tests can run in parallel without race conditions in accessing states. If there's no state intervention. you can also reuse the `Worker` to speedup the tests. + 2. The worker has a `root` account. For `SandboxWorker`, it's `test.near`. For `TestnetWorker`, it creates a unique account. The following accounts are created as subaccounts of the root account. The name of the account will change from different runs, so you should not refer to them by hard coded account name. You can access them via the account object, such as `root`, `alice` and `contract` above. + 3. `root.createSubAccount` creates a new subaccount of `root` with the given name, for example `alice.`. + 4. `root.createAndDeploy` creates an account with the given name, `contract-name.`, then deploys the specified Wasm file to it. + 5. `path/to/compiled.wasm` will resolve relative to your project root. That is, the nearest directory with a `package.json` file, or your current working directory if no `package.json` is found. To construct a path relative to your test file, you can use `path.join(__dirname, '../etc/etc.wasm')` ([more info](https://nodejs.org/api/path.html#path_path_join_paths)). + 6. `worker` contains a reference to this data directory, so that multiple tests can use it as a starting point. + 7. If you're using a test framework, you can save the `worker` object and account objects `root`, `alice`, `contract` to test context to reuse them in subsequent tests. + 8. At the end of test, call `await worker.tearDown()` to shuts down the Worker. It gracefully shuts down the Sandbox instance it ran in the background. However, it keeps the data directory around. That's what stores the state of the two accounts that were created (`alice` and `contract-account-name` with its deployed contract). + +--- + +**Writing tests** + + `near-workspaces` is designed for concurrency. Here's a simple way to get concurrent runs using plain JS: + + ```ts + import {strict as assert} from 'assert'; + + await Promise.all([ + async () => { + await alice.call( + contract, + 'some_update_function', + {some_string_argument: 'cool', some_number_argument: 42} + ); + const result = await contract.view( + 'some_view_function', + {account_id: alice} + ); + assert.equal(result, 'whatever'); + }, + async () => { + const result = await contract.view( + 'some_view_function', + {account_id: alice} + ); + /* Note that we expect the value returned from `some_view_function` to be + a default here, because this `fork` runs *at the same time* as the + previous, in a separate local blockchain */ + assert.equal(result, 'some default'); + } + ]); + ``` + + Let's step through this. + + 1. `worker` and accounts such as `alice` are created before. + 2. `call` syntax mirrors [near-cli](https://github.com/near/near-cli) and either returns the successful return value of the given function or throws the encountered error. If you want to inspect a full transaction and/or avoid the `throw` behavior, you can use `callRaw` instead. + 3. While `call` is invoked on the account _doing the call_ (`alice.call(contract, …)`), `view` is invoked on the account _being viewed_ (`contract.view(…)`). This is because the caller of a view is irrelevant and ignored. + 4. Gotcha: the full account names does not match the strings passed to `createSubAccount` and `createAndDeploy`, which is why you must write `alice.call(contract, …)` rather than `alice.call('contract-account-name', …)`. But! The `Account` class overrides `toJSON` so that you can pass `{account_id: alice}` in arguments rather than `{account_id: alice.accountId}`. If you need the generated account ID in some other circumstance, remember to use `alice.accountId`. + + +See the [tests](https://github.com/near/workspaces-js/tree/main/__tests__) directory in this project for more examples. + + + + +1. Initialing a `Worker` + + +First, you need to declare some imports: + +```rust +// macro allowing us to convert human readable units to workspace units. +use near_units::parse_near; + +// macro allowing us to convert args into JSON bytes to be read by the contract. +use serde_json::json; + +// Additional convenient imports that allows workspaces to function readily. +use workspaces::prelude::*; +``` + +You'll need to have your pre-compiled WASM contract ahead of time and know its path. + +```rust +// In this example, we will be pointing to the example's NFT contract +const NFT_WASM_FILEPATH: &str = "./examples/res/non_fungible_token.wasm"; +``` + +This includes launching the sandbox, loading your wasm file and deploying it to the sandbox environment. + +```rust + +#[tokio::test] +async fn test_nft_contract() -> anyhow::Result<()> { + let worker = workspaces::sandbox().await?; + let wasm = std::fs::read(NFT_WASM_FILEPATH)?; + let contract = worker.dev_deploy(&wasm).await?; +``` +Where +* `anyhow` - A crate that deals with error handling, making it more robust for developers. +* `worker` - Our gateway towards interacting with our sandbox environment. +* `contract`- The deployed contract on sandbox the developer interacts with. + +--- + +**Writing tests** + +Then you'll go directly into making a call into the contract, and initialize the NFT contract's metadata: + +```rust + let outcome = contract + .call(&worker, "new_default_meta") + .args_json(json!({ + "owner_id": contract.id(), + }))? + .transact() + .await?; + + // outcome contains data like logs, receipts and transaction outcomes. + println!("new_default_meta outcome: {:#?}", outcome); +``` + +Next, let's mint an NFT via `nft_mint`. This showcases some extra arguments you can supply, such as deposit and gas: + +```rust + let deposit = 10000000000000000000000; + let outcome = contract + .call(&worker, "nft_mint") + .args_json(json!({ + "token_id": "0", + "token_owner_id": contract.id(), + "token_metadata": { + "title": "Olympus Mons", + "dscription": "Tallest mountain in charted solar system", + "copies": 1, + }, + }))? + .deposit(deposit) + // nft_mint might consume more than default gas, so supply our own gas value: + .gas(near_units::parse_gas("300 T")) + .transact() + .await?; + + println!("nft_mint outcome: {:#?}", outcome); +``` + +Then, you can view the minted NFT's metadata using a `view` call to `nft_metadata`: + +```rust + let result: serde_json::Value = contract + .call(&worker, "nft_metadata") + .view() + .await? + .json()?; + + println!("--------------\n{}", result); + println!("Dev Account ID: {}", contract.id()); + Ok(()) +} +``` + + + + + + +## "Spooning" Contracts from Testnet and Mainnet + + +[Spooning a blockchain](https://coinmarketcap.com/alexandria/glossary/spoon-blockchain) is copying the data from one network into a different network. near-workspaces makes it easy to copy data from Mainnet or Testnet contracts into your local Sandbox environment: + + + + +```ts +const refFinance = await root.importContract({ + mainnetContract: 'v2.ref-finance.near', + blockId: 50_000_000, + withData: true, +}); +``` + +This would copy the Wasm bytes and contract state from [v2.ref-finance.near](https://explorer.near.org/accounts/v2.ref-finance.near) to your local blockchain as it existed at block `50_000_000`. This makes use of Sandbox's special [patch state](#patch-state-on-the-fly) feature to keep the contract name the same, even though the top level account might not exist locally (note that this means it only works in Sandbox testing mode). You can then interact with the contract in a deterministic way the same way you interact with all other accounts created with near-workspaces. +:::note +`withData` will only work out-of-the-box if the contract's data is 50kB or less. This is due to the default configuration of RPC servers; see [the "Heads Up" note here](https://docs.near.org/docs/api/rpc/contracts#view-contract-state). +::: + +See a [TypeScript example of spooning](https://github.com/near/workspaces-js/blob/main/__tests__/05.spoon-contract-to-sandbox.ava.ts) contracts. + + + + +Specify the contract name from `testnet` you want to be pulling, and a specific block ID referencing back to a specific time. (Just in case the contract you're referencing has been changed or updated) + +```rust +const CONTRACT_ACCOUNT: &str = "contract_account_name_on_testnet.testnet"; +const BLOCK_HEIGHT: BlockHeight = 12345; +``` + + +Create a function called `pull_contract` which will pull the contract's `.wasm` file from the chain and deploy it onto your local sandbox. You'll have to re-initialize it with all the data to run tests. + +```rust +async fn pull_contract(owner: &Account, worker: &Worker) -> anyhow::Result { + let testnet = workspaces::testnet_archival(); + let contract_id: AccountId = CONTRACT_ACCOUNT.parse()?; +``` + +This next line will actually pull down the relevant contract from testnet and set an initial balance on it with 1000 NEAR. + +```rust + let contract = worker + .import_contract(&contract_id, &testnet) + .initial_balance(parse_near!("1000 N")) + .block_height(BLOCK_HEIGHT) + .transact() + .await?; +``` + +Following that you'll have to init the contract again with your metadata. +This is because the contract's data is too big for the RPC service to pull down. (limits are set to 50Mb) + +```rust + owner + .call(&worker, contract.id(), "init_method_name") + .args_json(serde_json::json!({ + "arg1": value1, + "arg2": value2, + }))? + .transact() + .await?; + + Ok(contract) +} +``` + + + + + + + +## Running on Testnet + +near-workspaces is set up so that you can write tests once and run them against a local Sandbox node (the default behavior) or against [NEAR TestNet](https://docs.near.org/docs/concepts/networks). Some reasons this might be helpful: + +* Gives higher confidence that your contracts work as expected +* You can test against deployed testnet contracts +* If something seems off in Sandbox mode, you can compare it to testnet + +:::tip +In order to use Workspaces in testnet mode you will need to have a `testnet` account. +You can create one [here](https://wallet.testnet.near.org/). +::: + +You can switch to testnet mode in three ways. + +1. When creating Worker set network to `testnet` and pass your master account: + + + + + ```ts + const worker = await Worker.init({ + network: 'testnet', + testnetMasterAccountId: '', + }) + ``` + + + + + ```rust + #[tokio::main] // or whatever runtime we want + async fn main() -> anyhow::Result<()> { + // Create a sandboxed environment. + // NOTE: Each call will create a new sandboxed environment + let worker = workspaces::sandbox().await?; + // or for testnet: + let worker = workspaces::testnet().await?; + } + ``` + + + + + +2. Set the `NEAR_WORKSPACES_NETWORK` and `TESTNET_MASTER_ACCOUNT_ID` environment variables when running your tests: + + + + + ```bash + NEAR_WORKSPACES_NETWORK=testnet TESTNET_MASTER_ACCOUNT_ID= node test.js + ``` + + If you set this environment variables and pass `{network: 'testnet', testnetMasterAccountId: }` to `Worker.init`, the config object takes precedence. + + + + +3. If using `near-workspaces` with AVA, you can use a custom config file. Other test runners allow similar config files; adjust the following instructions for your situation. + + + + + Create a file in the same directory as your `package.json` called `ava.testnet.config.cjs` with the following contents: + + ```js + module.exports = { + ...require('near-workspaces/ava.testnet.config.cjs'), + ...require('./ava.config.cjs'), + }; + module.exports.environmentVariables = { + TESTNET_MASTER_ACCOUNT_ID: '', + }; + ``` + + The [near-workspaces/ava.testnet.config.cjs](https://github.com/near/workspaces-js/blob/main/ava.testnet.config.cjs) import sets the `NEAR_WORKSPACES_NETWORK` environment variable for you. A benefit of this approach is that you can then easily ignore files that should only run in Sandbox mode. + + Now you'll also want to add a `test:testnet` script to your `package.json`'s `scripts` section: + + ```diff + "scripts": { + "test": "ava", + + "test:testnet": "ava --config ./ava.testnet.config.cjs" + } + ``` + + + + +## Patch State on the Fly + +In Sandbox-mode, you can add or modify any contract state, contract code, account or access key with `patchState`. + +You cannot perform arbitrary mutation on contract state with transactions since transactions can only include contract calls that mutate state in a contract-programmed way. For example, with an NFT contract, you can perform some operation with NFTs you have ownership of, but you cannot manipulate NFTs that are owned by other accounts since the smart contract is coded with checks to reject that. This is the expected behavior of the NFT contract. However, you may want to change another person's NFT for a test setup. This is called "arbitrary mutation on contract state" and can be done with `patchState`. Alternatively you can stop the node, dump state at genesis, edit genesis, and restart the node. The later approach is more complicated to do and also cannot be performed without restarting the node. + +It is true that you can alter contract code, accounts, and access keys using normal transactions via the `DeployContract`, `CreateAccount`, and `AddKey` [actions](https://nomicon.io/RuntimeSpec/Actions#addkeyaction). But this limits you to altering your own account or sub-account. `patchState` allows you to perform these operations on any account. + +To see an example of how to do this, see the [patch-state test](https://github.com/near/workspaces-js/blob/main/__tests__/02.patch-state.ava.ts). + + +## Time Traveling + +`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: + + + + +:::note +Time Traveling in `workspaces-js` is currently unavailable. +::: + + + + +```rust +#[tokio::test] +async fn test_contract() -> anyhow::Result<()> { + let worker = workspaces::sandbox().await?; + let contract = worker.dev_deploy(WASM_BYTES); + + let blocks_to_advance = 10000; + worker.fast_forward(blocks_to_advance); + + // Now, "do_something_with_time" will be in the future and can act on future time-related state. + contract.call(&worker, "do_something_with_time") + .transact() + .await?; +} +``` + + + + +For a full Rust example, take a look at [examples/src/fast_forward.rs](https://github.com/near/workspaces-rs/blob/main/examples/src/fast_forward.rs). + + +## Tutorials From 87be1a2103809d9d77be1ebb50bfc867661dc2dd Mon Sep 17 00:00:00 2001 From: Damian Parrino Date: Fri, 6 May 2022 20:28:46 -0300 Subject: [PATCH 3/6] Update workspaces.md clean up --- docs/develop/contracts/workspaces.md | 122 +++++++- .../contracts/workspaces/workspaces-js.md | 283 +----------------- .../contracts/workspaces/workspaces-rs.md | 270 +---------------- 3 files changed, 109 insertions(+), 566 deletions(-) diff --git a/docs/develop/contracts/workspaces.md b/docs/develop/contracts/workspaces.md index fe3ce7b7ac0..385744d4f31 100644 --- a/docs/develop/contracts/workspaces.md +++ b/docs/develop/contracts/workspaces.md @@ -7,19 +7,19 @@ sidebar_label: NEAR Workspaces import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -NEAR Workspaces is a library for automating workflows and writing tests for NEAR smart contracts. +NEAR Workspaces lets you automate workflows and write tests for NEAR smart contracts. You can use it as-is or integrate it with a test runner of your choice (AVA, Jest, Mocha, etc.). If you don't have a preference, we suggest you to use AVA. ## Overview -Controlled, concurrent workspaces in local NEAR Sandbox blockchains or on NEAR TestNet. Fun, deterministic testing and powerful scripting for NEAR. - -Write tests once, run them both on NEAR TestNet and a controlled NEAR Sandbox local environment +NEAR Workspaces provide controlled, concurrent workspaces in a local NEAR Sandbox blockchain or on NEAR TestNet. +This allows you write tests once, and run them both on `testnet` and on a controlled Sandbox local environment, +enabling deterministic testing and powerful scripting for NEAR smart contracts. ## Libraries -The same Workspaces interface is supported by the following libraries: +The Workspaces interface is supported by the following libraries: | Language | Link | |----------|------| @@ -29,22 +29,24 @@ The same Workspaces interface is supported by the following libraries: - TypeScript/JavaScript :::note - Current version of `workspaces-js` does not support the "Time Traveling" feature provided by the `fast_forward` method. This will be addressed in a future release. + The current version of `workspaces-js` does not support the "Time Traveling" feature provided by the `fast_forward` method. This will be addressed in a future release. ::: - Rust :::note - Current version of `workspaces-rs` does not support macOS on M1 chip devices due to internal upgrades with wasmer. M1 users should use `workspaces-rs` version `0.1.1` until this problem gets resolved. + The current version of `workspaces-rs` does not support macOS on M1 chip devices due to internal upgrades with wasmer. M1 users should use `workspaces-rs` version `0.1.1` until this issue is resolved. ::: ## Quick Start -To get started with NEAR Workspaces you need to do only two things: +To get started with NEAR Workspaces you need to do two things: 1. Initialize a `Worker`. + - A worker is the gateway towards interacting with your sandbox environment. 2. Write tests. + - See the JavaScript and Rust examples below: @@ -225,7 +227,7 @@ Then, you can view the minted NFT's metadata using a `view` call to `nft_metadat ## "Spooning" Contracts from Testnet and Mainnet -[Spooning a blockchain](https://coinmarketcap.com/alexandria/glossary/spoon-blockchain) is copying the data from one network into a different network. near-workspaces makes it easy to copy data from Mainnet or Testnet contracts into your local Sandbox environment: +[Spooning a blockchain](https://coinmarketcap.com/alexandria/glossary/spoon-blockchain) is copying the data from one network into a different network. NEAR Workspaces makes it easy to copy data from Mainnet or Testnet contracts into your local Sandbox environment: @@ -297,11 +299,9 @@ This is because the contract's data is too big for the RPC service to pull down. - - ## Running on Testnet -near-workspaces is set up so that you can write tests once and run them against a local Sandbox node (the default behavior) or against [NEAR TestNet](https://docs.near.org/docs/concepts/networks). Some reasons this might be helpful: +NEAR Workspaces is set up so that you can write tests once and run them against a local Sandbox node (the default behavior) or against [NEAR TestNet](https://docs.near.org/docs/concepts/networks). Some reasons this might be helpful: * Gives higher confidence that your contracts work as expected * You can test against deployed testnet contracts @@ -393,12 +393,101 @@ You can switch to testnet mode in three ways. In Sandbox-mode, you can add or modify any contract state, contract code, account or access key with `patchState`. -You cannot perform arbitrary mutation on contract state with transactions since transactions can only include contract calls that mutate state in a contract-programmed way. For example, with an NFT contract, you can perform some operation with NFTs you have ownership of, but you cannot manipulate NFTs that are owned by other accounts since the smart contract is coded with checks to reject that. This is the expected behavior of the NFT contract. However, you may want to change another person's NFT for a test setup. This is called "arbitrary mutation on contract state" and can be done with `patchState`. Alternatively you can stop the node, dump state at genesis, edit genesis, and restart the node. The later approach is more complicated to do and also cannot be performed without restarting the node. +:::tip +You can alter contract code, accounts, and access keys using normal transactions via the `DeployContract`, `CreateAccount`, and `AddKey` [actions](https://nomicon.io/RuntimeSpec/Actions#addkeyaction). But this limits you to altering your own account or sub-account. `patchState` allows you to perform these operations on any account. +::: + +Keep in mind that you cannot perform arbitrary mutation on contract state with transactions since transactions can only include contract calls that mutate state in a contract-programmed way. For example, with an NFT contract, you can perform some operation with NFTs you have ownership of, but you cannot manipulate NFTs that are owned by other accounts since the smart contract is coded with checks to reject that. This is the expected behavior of the NFT contract. However, you may want to change another person's NFT for a test setup. This is called "arbitrary mutation on contract state" and can be done with `patchState`: + + + + +```js + const {contract, ali} = t.context.accounts; + // Contract must have some state for viewState & patchState to work + await ali.call(contract, 'set_status', {message: 'hello'}); + // Get state + const state = await contract.viewState(); + // Get raw value + const statusMessage = state.get('STATE', {schema, type: StatusMessage}); + // Update contract state + statusMessage.records.push( + new BorshRecord({k: 'alice.near', v: 'hello world'}), + ); + // Serialize and patch state back to runtime + await contract.patchState( + 'STATE', + borsh.serialize(schema, statusMessage), + ); + // Check again that the update worked + const result = await contract.view('get_status', { + account_id: 'alice.near', + }); + t.is(result, 'hello world'); +``` + +To see a complete example of how to do this, see the [patch-state test](https://github.com/near/workspaces-js/blob/main/__tests__/02.patch-state.ava.ts). -It is true that you can alter contract code, accounts, and access keys using normal transactions via the `DeployContract`, `CreateAccount`, and `AddKey` [actions](https://nomicon.io/RuntimeSpec/Actions#addkeyaction). But this limits you to altering your own account or sub-account. `patchState` allows you to perform these operations on any account. + + + +```rust + // Grab STATE from the testnet status_message contract. This contract contains the following data: + // get_status(dev-20211013002148-59466083160385) => "hello from testnet" + let (testnet_contract_id, status_msg) = { + let worker = workspaces::testnet().await?; + let contract_id: AccountId = TESTNET_PREDEPLOYED_CONTRACT_ID + .parse() + .map_err(anyhow::Error::msg)?; -To see an example of how to do this, see the [patch-state test](https://github.com/near/workspaces-js/blob/main/__tests__/02.patch-state.ava.ts). + let mut state_items = worker.view_state(&contract_id, None).await?; + let state = state_items.remove(b"STATE".as_slice()).unwrap(); + let status_msg = StatusMessage::try_from_slice(&state)?; + + (contract_id, status_msg) + }; + + info!(target: "spooning", "Testnet: {:?}", status_msg); + + // Create our sandboxed environment and grab a worker to do stuff in it: + let worker = workspaces::sandbox().await?; + + // Deploy with the following status_message state: sandbox_contract_id => "hello from sandbox" + let sandbox_contract = deploy_status_contract(&worker, "hello from sandbox").await?; + + // Patch our testnet STATE into our local sandbox: + worker + .patch_state( + sandbox_contract.id(), + "STATE".as_bytes(), + &status_msg.try_to_vec()?, + ) + .await?; + + // Now grab the state to see that it has indeed been patched: + let status: String = sandbox_contract + .view( + &worker, + "get_status", + serde_json::json!({ + "account_id": testnet_contract_id, + }) + .to_string() + .into_bytes(), + ) + .await? + .json()?; + + info!(target: "spooning", "New status patched: {:?}", status); + assert_eq!(&status, "hello from testnet"); +``` + + + + +As an alternative to `patchState`, you can stop the node, dump state at genesis, edit the genesis, and restart the node. +This approach is more complex to do and also cannot be performed without restarting the node. ## Time Traveling @@ -437,3 +526,6 @@ For a full Rust example, take a look at [examples/src/fast_forward.rs](https://g ## Tutorials + +- Using Workspaces in [JavaScript](https://github.com/near/workspaces-js) +- Using Workspaces in [Rust](https://github.com/near/workspaces-rs) diff --git a/docs/tutorials/contracts/workspaces/workspaces-js.md b/docs/tutorials/contracts/workspaces/workspaces-js.md index dc5526a1ae6..275bb5b8db2 100644 --- a/docs/tutorials/contracts/workspaces/workspaces-js.md +++ b/docs/tutorials/contracts/workspaces/workspaces-js.md @@ -5,285 +5,4 @@ sidebar_label: Workspaces JS --- -`NEAR Workspaces` is a library for automating workflows and writing tests for NEAR smart contracts. You can use it as is or integrate with test runner of your choise (AVA, Jest, Mocha, etc.). If you don't have a preference, we suggest you to use AVA. - -## Quick Start (without testing frameworks) - -To get started with NEAR Workspaces you need to do only two things: - -1. Initialize a `Worker`. - - ```ts - const worker = await Worker.init(); - const root = worker.rootAccount; - - const alice = await root.createSubAccount('alice'); - const contract = await root.createAndDeploy( - root.getSubAccount('contract-name').accountId, - 'path/to/compiled.wasm' - ); - ``` - - Let's step through this. - - 1. `Worker.init` initializes a new `SandboxWorker` or `TestnetWorker` depending on the config. `SandboxWorker` contains [NEAR Sandbox](https://docs.near.org/docs/develop/contracts/sandbox), which is essentially a local mini-NEAR blockchain. You can create one `Worker` per test to get its own data directory and port (for Sandbox) or root account (for Testnet), so that tests can run in parallel without race conditions in accessing states. If there's no state intervention. you can also reuse the `Worker` to speedup the tests. - 2. The worker has a `root` account. For `SandboxWorker`, it's `test.near`. For `TestnetWorker`, it creates a unique account. The following accounts are created as subaccounts of the root account. The name of the account will change from different runs, so you should not refer to them by hard coded account name. You can access them via the account object, such as `root`, `alice` and `contract` above. - 3. `root.createSubAccount` creates a new subaccount of `root` with the given name, for example `alice.`. - 4. `root.createAndDeploy` creates an account with the given name, `contract-name.`, then deploys the specified Wasm file to it. - 5. `path/to/compiled.wasm` will resolve relative to your project root. That is, the nearest directory with a `package.json` file, or your current working directory if no `package.json` is found. To construct a path relative to your test file, you can use `path.join(__dirname, '../etc/etc.wasm')` ([more info](https://nodejs.org/api/path.html#path_path_join_paths)). - 6. `worker` contains a reference to this data directory, so that multiple tests can use it as a starting point. - 7. If you're using a test framework, you can save the `worker` object and account objects `root`, `alice`, `contract` to test context to reuse them in subsequent tests. - 8. At the end of test, call `await worker.tearDown()` to shuts down the Worker. It gracefully shuts down the Sandbox instance it ran in the background. However, it keeps the data directory around. That's what stores the state of the two accounts that were created (`alice` and `contract-account-name` with its deployed contract). - -2. Writing tests. - - `near-workspaces` is designed for concurrency. Here's a simple way to get concurrent runs using plain JS: - - ```ts - import {strict as assert} from 'assert'; - - await Promise.all([ - async () => { - await alice.call( - contract, - 'some_update_function', - {some_string_argument: 'cool', some_number_argument: 42} - ); - const result = await contract.view( - 'some_view_function', - {account_id: alice} - ); - assert.equal(result, 'whatever'); - }, - async () => { - const result = await contract.view( - 'some_view_function', - {account_id: alice} - ); - /* Note that we expect the value returned from `some_view_function` to be - a default here, because this `fork` runs *at the same time* as the - previous, in a separate local blockchain */ - assert.equal(result, 'some default'); - } - ]); - ``` - - Let's step through this. - - 1. `worker` and accounts such as `alice` are created before. - 2. `call` syntax mirrors [near-cli](https://github.com/near/near-cli) and either returns the successful return value of the given function or throws the encountered error. If you want to inspect a full transaction and/or avoid the `throw` behavior, you can use `callRaw` instead. - 3. While `call` is invoked on the account _doing the call_ (`alice.call(contract, …)`), `view` is invoked on the account _being viewed_ (`contract.view(…)`). This is because the caller of a view is irrelevant and ignored. - 4. Gotcha: the full account names does not match the strings passed to `createSubAccount` and `createAndDeploy`, which is why you must write `alice.call(contract, …)` rather than `alice.call('contract-account-name', …)`. But! The `Account` class overrides `toJSON` so that you can pass `{account_id: alice}` in arguments rather than `{account_id: alice.accountId}`. If you need the generated account ID in some other circumstance, remember to use `alice.accountId`. - - -See the [tests](https://github.com/near/workspaces-js/tree/main/__tests__) directory in this project for more examples. - -## Quick Start with AVA - -Since `near-workspaces` is designed for concurrency, AVA is a great fit, because it runs tests concurrently by default. To use`NEAR Workspaces` with AVA: - 1. Start with the basic setup described [here](https://github.com/avajs/ava). - 2. Add custom script for running tests on Testnet (if needed). Check instructions in `Running on Testnet` section. - 3. Add your tests following these example: - - ```ts - import {Worker} from 'near-workspaces'; - import anyTest, {TestFn} from 'ava' - - const test = anyTest as TestFn<{ - worker: Worker; - accounts: Record; - }>; - - /* If using `test.before`, each test is reusing the same worker; - If you'd like to make a copy of the worker, use `beforeEach` after `afterEach`, - which allows you to isolate the state for each test */ - test.before(async t => { - const worker = await Worker.init(); - const root = worker.rootAccount; - const contract = await root.createAndDeploy( - 'account-id-for-contract', - 'path/to/contract/file.wasm', - ); - /* Account that you will be able to use in your tests */ - const ali = await root.createSubAccount('ali'); - t.context.worker = worker; - t.context.accounts = {root, contract, ali}; - }) - - test('Test name', async t => { - const {ali, contract} = t.context.accounts; - await ali.call(contract, 'set_status', {message: 'hello'}); - const result: string = await contract.view('get_status', {account_id: ali}); - t.is(result, 'hello'); - }); - - test.after(async t => { - // Stop Sandbox server - await t.context.worker.tearDown().catch(error => { - console.log('Failed to stop the Sandbox:', error); - }); - }); - ``` - - -## "Spooning" Contracts from Testnet and Mainnet - -[Spooning a blockchain](https://coinmarketcap.com/alexandria/glossary/spoon-blockchain) is copying the data from one network into a different network. near-workspaces makes it easy to copy data from Mainnet or Testnet contracts into your local Sandbox environment: - -```ts -const refFinance = await root.importContract({ - mainnetContract: 'v2.ref-finance.near', - blockId: 50_000_000, - withData: true, -}); -``` - -This would copy the Wasm bytes and contract state from [v2.ref-finance.near](https://explorer.near.org/accounts/v2.ref-finance.near) to your local blockchain as it existed at block `50_000_000`. This makes use of Sandbox's special [patch state](#patch-state-on-the-fly) feature to keep the contract name the same, even though the top level account might not exist locally (note that this means it only works in Sandbox testing mode). You can then interact with the contract in a deterministic way the same way you interact with all other accounts created with near-workspaces. - -Gotcha: `withData` will only work out-of-the-box if the contract's data is 50kB or less. This is due to the default configuration of RPC servers; see [the "Heads Up" note here](https://docs.near.org/docs/api/rpc/contracts#view-contract-state). Some teams at NEAR are hard at work giving you an easy way to run your own RPC server, at which point you can point tests at your custom RPC endpoint and get around the 50kB limit. - -See an [example of spooning](https://github.com/near/workspaces-js/blob/main/__tests__/05.spoon-contract-to-sandbox.ava.ts) contracts. - -## Running on Testnet - -near-workspaces is set up so that you can write tests once and run them against a local Sandbox node (the default behavior) or against [NEAR TestNet](https://docs.near.org/docs/concepts/networks). Some reasons this might be helpful: - -* Gives higher confidence that your contracts work as expected -* You can test against deployed testnet contracts -* If something seems off in Sandbox mode, you can compare it to testnet - -In order to use Workspaces JS in testnet mode you will need to have a testnet account. You can create one [here](https://wallet.testnet.near.org/). - -You can switch to testnet mode in three ways. - -1. When creating Worker set network to `testnet` and pass your master account: - - ```ts - const worker = await Worker.init({ - network: 'testnet', - testnetMasterAccountId: '', - }) - ``` - -2. Set the `NEAR_WORKSPACES_NETWORK` and `TESTNET_MASTER_ACCOUNT_ID` environment variables when running your tests: - - ```bash - NEAR_WORKSPACES_NETWORK=testnet TESTNET_MASTER_ACCOUNT_ID= node test.js - ``` - - If you set this environment variables and pass `{network: 'testnet', testnetMasterAccountId: }` to `Worker.init`, the config object takes precedence. - -3. If using `near-workspaces` with AVA, you can use a custom config file. Other test runners allow similar config files; adjust the following instructions for your situation. - - Create a file in the same directory as your `package.json` called `ava.testnet.config.cjs` with the following contents: - - ```js - module.exports = { - ...require('near-workspaces/ava.testnet.config.cjs'), - ...require('./ava.config.cjs'), - }; - module.exports.environmentVariables = { - TESTNET_MASTER_ACCOUNT_ID: '', - }; - ``` - - The [near-workspaces/ava.testnet.config.cjs](https://github.com/near/workspaces-js/blob/main/ava.testnet.config.cjs) import sets the `NEAR_WORKSPACES_NETWORK` environment variable for you. A benefit of this approach is that you can then easily ignore files that should only run in Sandbox mode. - - Now you'll also want to add a `test:testnet` script to your `package.json`'s `scripts` section: - - ```diff - "scripts": { - "test": "ava", - + "test:testnet": "ava --config ./ava.testnet.config.cjs" - } - ``` - - -### Stepping through a testnet example - -Let's revisit a shortened version of the example from How It Works above, describing what will happen in Testnet. - -1. Create a `Worker`. - - ```ts - const worker = await Worker.init(); - ``` - - `Worker.init` creates a unique testnet account as root account. - -2. Write tests. - - ```ts - await Promise.all([ - async () => { - await alice.call( - contract, - 'some_update_function', - {some_string_argument: 'cool', some_number_argument: 42} - ); - const result = await contract.view( - 'some_view_function', - {account_id: alice} - ); - assert.equal(result, 'whatever'); - }, - async () => { - const result = await contract.view( - 'some_view_function', - {account_id: alice} - ); - assert.equal(result, 'some default'); - } - ]); - ``` - -Note: Sometimes account creation rate limits are reached on testnet, simply wait a little while and try again. - -### Running tests only in Sandbox - -If some of your runs take advantage of Sandbox-specific features, you can skip these on testnet in two ways: - -1. You can skip entire sections of your files by checking `getNetworkFromEnv() === 'sandbox'`. - - ```ts - let worker = Worker.init(); - // things make sense to any network - const root = worker.rootAccount; - const alice = await root.createSubAccount('alice'); - - if (getNetworkFromEnv() === 'sandbox') { - // thing that only makes sense with sandbox - } - ``` - -2. Use a separate testnet config file, as described under the "Running on Testnet" heading above. Specify test files to include and exclude in config file. - -## Patch State on the Fly - -In Sandbox-mode, you can add or modify any contract state, contract code, account or access key with `patchState`. - -You cannot perform arbitrary mutation on contract state with transactions since transactions can only include contract calls that mutate state in a contract-programmed way. For example, with an NFT contract, you can perform some operation with NFTs you have ownership of, but you cannot manipulate NFTs that are owned by other accounts since the smart contract is coded with checks to reject that. This is the expected behavior of the NFT contract. However, you may want to change another person's NFT for a test setup. This is called "arbitrary mutation on contract state" and can be done with `patchState`. Alternatively you can stop the node, dump state at genesis, edit genesis, and restart the node. The later approach is more complicated to do and also cannot be performed without restarting the node. - -It is true that you can alter contract code, accounts, and access keys using normal transactions via the `DeployContract`, `CreateAccount`, and `AddKey` [actions](https://nomicon.io/RuntimeSpec/Actions.html?highlight=actions#actions). But this limits you to altering your own account or sub-account. `patchState` allows you to perform these operations on any account. - -To see an example of how to do this, see the [patch-state test](https://github.com/near/workspaces-js/blob/main/__tests__/02.patch-state.ava.ts). - -## Pro Tips - -* `NEAR_WORKSPACES_DEBUG=true` – run tests with this environment variable set to get copious debug output and a full log file for each Sandbox instance. - -* `Worker.init` [config](https://github.com/near/workspaces-js/blob/main/packages/js/src/interfaces.ts) – you can pass a config object as the first argument to `Worker.init`. This lets you do things like: - - * skip initialization if specified data directory already exists (the default behavior) - - ```ts - Worker.init( - { rm: false, homeDir: './test-data/alice-owns-an-nft' }, - ) - ``` - - * always recreate such data directory instead with `rm: true` - - * specify which port to run on - - * and more! +Tutorial for Workspaces JS diff --git a/docs/tutorials/contracts/workspaces/workspaces-rs.md b/docs/tutorials/contracts/workspaces/workspaces-rs.md index fca23c5cb97..c1dd42a0884 100644 --- a/docs/tutorials/contracts/workspaces/workspaces-rs.md +++ b/docs/tutorials/contracts/workspaces/workspaces-rs.md @@ -4,273 +4,5 @@ title: NEAR Workspaces (Rust Edition) sidebar_label: Workspaces Rust --- -Rust library for automating workflows and writing tests for NEAR smart contracts. This software is not final, and will likely change. +Tutorial for Workspaces Rust -## Release notes - -**Release notes and unreleased changes can be found in the [CHANGELOG](CHANGELOG.md)** - -## Requirements - -- Rust v1.56 and up -- MacOS (x86) or Linux (x86) for sandbox tests. Testnet is available regardless - -### M1 MacOS - -NOTE: Current version of `workspaces-rs` does not support use on M1 chip devices due to internal upgrades with wasmer. M1 users should use `workspaces-rs` version `0.1.1` until this problem gets resolved. Check the progress on this issue [here](https://github.com/near/workspaces-rs/issues/110). - -Even with the above note, we can use `workspaces-rs` with version `0.1.1` on M1 by setting up rosetta plus our cross compile target: -``` -softwareupdate --install-rosetta -rustup default stable-x86_64-apple-darwin -``` - -### WASM compilation not supported - -`workspaces-rs`, the library itself, does not currently compile to WASM. Best to put this dependency in `[dev-dependencies]` section of `Cargo.toml` if we were trying to run this library alongside something that already does compile to WASM, such as `near-sdk-rs`. - -## Simple Testing Case - -A simple test to get us going and familiar with `workspaces` framework. Here, we will be going through the NFT contract and how we can test it with `workspaces-rs`. - -### Setup -- Imports -First, we need to declare some imports for convenience. - -```rust -// macro allowing us to convert human readable units to workspace units. -use near_units::parse_near; - -// macro allowing us to convert args into JSON bytes to be read by the contract. -use serde_json::json; - -// Additional convenient imports that allows workspaces to function readily. -use workspaces::prelude::*; -``` - -We will need to have our pre-compiled WASM contract ahead of time and know its path. In this showcase, we will be pointing to the example's NFT contract: - -```rust -const NFT_WASM_FILEPATH: &str = "./examples/res/non_fungible_token.wasm"; -``` - -NOTE: there is an unstable feature that will allow us to compile our projects during testing time as well. Take a look at the feature section [Compiling Contracts During Test Time](#compiling-contracts-during-test-time) - -### Setup -- Setting up Sandbox and Deploying NFT Contract - -This includes launching our sandbox, loading our wasm file and deploying that wasm file to the sandbox environment. - -```rust - -#[tokio::test] -async fn test_nft_contract() -> anyhow::Result<()> { - let worker = workspaces::sandbox().await?; - let wasm = std::fs::read(NFT_WASM_FILEPATH)?; - let contract = worker.dev_deploy(&wasm).await?; -``` -Where -* `anyhow` - A crate that deals with error handling, making it more robust for developers. -* `worker` - Our gateway towards interacting with our sandbox environment. -* `contract`- The deployed contract on sandbox the developer interacts with. - -Then we'll go directly into making a call into the contract, and initialize the NFT contract's metadata: -```rust - let outcome = contract - .call(&worker, "new_default_meta") - .args_json(json!({ - "owner_id": contract.id(), - }))? - .transact() - .await?; - - // outcome contains data like logs, receipts and transaction outcomes. - println!("new_default_meta outcome: {:#?}", outcome); -``` - -Afterwards, let's mint an NFT via `nft_mint`. This showcases some extra arguments we can supply, such as deposit and gas: - -```rust - let deposit = 10000000000000000000000; - let outcome = contract - .call(&worker, "nft_mint") - .args_json(json!({ - "token_id": "0", - "token_owner_id": contract.id(), - "token_metadata": { - "title": "Olympus Mons", - "dscription": "Tallest mountain in charted solar system", - "copies": 1, - }, - }))? - .deposit(deposit) - // nft_mint might consume more than default gas, so supply our own gas value: - .gas(near_units::parse_gas("300 T")) - .transact() - .await?; - - println!("nft_mint outcome: {:#?}", outcome); -``` -Then later on, we can view our minted NFT's metadata via our `view` call into `nft_metadata`: -```rust - let result: serde_json::Value = contract - .call(&worker, "nft_metadata") - .view() - .await? - .json()?; - - println!("--------------\n{}", result); - println!("Dev Account ID: {}", contract.id()); - Ok(()) -} -``` - -## Examples - -More standalone examples can be found in `examples/src/*.rs`. - -To run the above NFT example, execute: -``` -cargo run --example nft -``` - -## Features - -### Choosing a network - -```rust -#[tokio::main] // or whatever runtime we want -async fn main() -> anyhow::Result<()> { - // Create a sandboxed environment. - // NOTE: Each call will create a new sandboxed environment - let worker = workspaces::sandbox().await?; - // or for testnet: - let worker = workspaces::testnet().await?; -} -``` - -### Helper Functions - -Need to make a helper function regardless of whatever Network? - -```rust -use workspaces::prelude::*; -use workspaces::{Contract, DevNetwork, Network, Worker}; - -// Helper function that calls into a contract we give it -async fn call_my_func(worker: Worker, contract: &Contract) -> anyhow::Result<()> { - // Call into the function `contract_function` with args: - contract.call(&worker, "contract_function") - .args_json(serde_json::json!({ - "message": msg, - })? - .transact() - .await?; - Ok(()) -} - -// Create a helper function that deploys a specific contract -// NOTE: `dev_deploy` is only available on `DevNetwork`s such sandbox and testnet. -async fn deploy_my_contract(worker: Worker) -> anyhow::Result { - worker.dev_deploy(&std::fs::read(CONTRACT_FILE)?).await -} -``` - -### Spooning - Pulling Existing State and Contracts from Mainnet/Testnet - -This example will showcase spooning state from a testnet contract into our local sandbox environment. - -We will first start with the usual imports: -```rust -use near_units::{parse_gas, parse_near}; -use workspaces::network::Sandbox; -use workspaces::prelude::*; -use workspaces::{Account, AccountId, BlockHeight, Contract, Worker}; -``` - -Then specify the contract name from testnet we want to be pulling: -```rust -const CONTRACT_ACCOUNT: &str = "contract_account_name_on_testnet.testnet"; -``` - -Let's also specify a specific block ID referencing back to a specific time. Just in case our contract or the one we're referencing has been changed or updated: - -```rust -const BLOCK_HEIGHT: BlockHeight = 12345; -``` - -Create a function called `pull_contract` which will pull the contract's `.wasm` file from the chain and deploy it onto our local sandbox. We'll have to re-initialize it with all the data to run tests. -```rust -async fn pull_contract(owner: &Account, worker: &Worker) -> anyhow::Result { - let testnet = workspaces::testnet_archival(); - let contract_id: AccountId = CONTRACT_ACCOUNT.parse()?; -``` - -This next line will actually pull down the relevant contract from testnet and set an initial balance on it with 1000 NEAR. - -Following that we will have to init the contract again with our own metadata. This is because the contract's data is to big for the RPC service to pull down, who's limits are set to 50mb. - -```rust - - let contract = worker - .import_contract(&contract_id, &testnet) - .initial_balance(parse_near!("1000 N")) - .block_height(BLOCK_HEIGHT) - .transact() - .await?; - - owner - .call(&worker, contract.id(), "init_method_name") - .args_json(serde_json::json!({ - "arg1": value1, - "arg2": value2, - }))? - .transact() - .await?; - - Ok(contract) -} -``` - -### Time Traveling - -`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: - -```rust -#[tokio::test] -async fn test_contract() -> anyhow::Result<()> { - let worker = workspaces::sandbox().await?; - let contract = worker.dev_deploy(WASM_BYTES); - - let blocks_to_advance = 10000; - worker.fast_forward(blocks_to_advance); - - // Now, "do_something_with_time" will be in the future and can act on future time-related state. - contract.call(&worker, "do_something_with_time") - .transact() - .await?; -} -``` - -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). - - -### Compiling Contracts During Test Time - -Note, this is an unstable feature and will very likely change. To enable it, add the `unstable` feature flag to `workspaces` dependency in `Cargo.toml`: - -```toml -[dependencies] -workspaces = { version = "...", features = ["unstable"] } -``` -Then, in our tests right before we call into `deploy` or `dev_deploy`, we can compile our projects: -```rust -#[tokio::test] -async fn test_contract() -> anyhow::Result<()> { - let wasm = workspaces::compile_project("path/to/contract-rs-project").await?; - - let worker = workspaces::sandbox().await?; - let contract = worker.dev_deploy(&wasm); - ... -} -``` - -For a full example, take a look at [workspaces/tests/deploy_project.rs](https://github.com/near/workspaces-rs/blob/main/workspaces/tests/deploy_project.rs). From c121be6d48655103168c9dad709c3c98f07f504d Mon Sep 17 00:00:00 2001 From: Damian Parrino Date: Mon, 9 May 2022 22:19:51 -0300 Subject: [PATCH 4/6] Update workspaces.md --- docs/develop/contracts/workspaces.md | 113 +++++++++++++++------------ 1 file changed, 63 insertions(+), 50 deletions(-) diff --git a/docs/develop/contracts/workspaces.md b/docs/develop/contracts/workspaces.md index 385744d4f31..af171002d8f 100644 --- a/docs/develop/contracts/workspaces.md +++ b/docs/develop/contracts/workspaces.md @@ -48,10 +48,12 @@ To get started with NEAR Workspaces you need to do two things: 2. Write tests. - See the JavaScript and Rust examples below: +### Initializing a Worker + -1. Initialing a `Worker` +1. Initializing a `Worker` ```ts const worker = await Worker.init(); @@ -75,9 +77,60 @@ To get started with NEAR Workspaces you need to do two things: 7. If you're using a test framework, you can save the `worker` object and account objects `root`, `alice`, `contract` to test context to reuse them in subsequent tests. 8. At the end of test, call `await worker.tearDown()` to shuts down the Worker. It gracefully shuts down the Sandbox instance it ran in the background. However, it keeps the data directory around. That's what stores the state of the two accounts that were created (`alice` and `contract-account-name` with its deployed contract). ---- -**Writing tests** + + + + +1. Initialing a `Worker` + + +First, you need to declare some imports: + +```rust +// macro allowing us to convert human readable units to workspace units. +use near_units::parse_near; + +// macro allowing us to convert args into JSON bytes to be read by the contract. +use serde_json::json; + +// Additional convenient imports that allows workspaces to function readily. +use workspaces::prelude::*; +``` + +You'll need to have your pre-compiled WASM contract ahead of time and know its path. + +```rust +// In this example, we will be pointing to the example's NFT contract +const NFT_WASM_FILEPATH: &str = "./examples/res/non_fungible_token.wasm"; +``` + +This includes launching the sandbox, loading your wasm file and deploying it to the sandbox environment. + +```rust + +#[tokio::test] +async fn test_nft_contract() -> anyhow::Result<()> { + let worker = workspaces::sandbox().await?; + let wasm = std::fs::read(NFT_WASM_FILEPATH)?; + let contract = worker.dev_deploy(&wasm).await?; +``` +Where +* `anyhow` - A crate that deals with error handling, making it more robust for developers. +* `worker` - Our gateway towards interacting with our sandbox environment. +* `contract`- The deployed contract on sandbox the developer interacts with. + + + + + + +### Writing tests + + + + +2. Writing tests `near-workspaces` is designed for concurrency. Here's a simple way to get concurrent runs using plain JS: @@ -123,49 +176,9 @@ See the [tests](https://github.com/near/workspaces-js/tree/main/__tests__) direc -1. Initialing a `Worker` - - -First, you need to declare some imports: - -```rust -// macro allowing us to convert human readable units to workspace units. -use near_units::parse_near; - -// macro allowing us to convert args into JSON bytes to be read by the contract. -use serde_json::json; - -// Additional convenient imports that allows workspaces to function readily. -use workspaces::prelude::*; -``` - -You'll need to have your pre-compiled WASM contract ahead of time and know its path. - -```rust -// In this example, we will be pointing to the example's NFT contract -const NFT_WASM_FILEPATH: &str = "./examples/res/non_fungible_token.wasm"; -``` - -This includes launching the sandbox, loading your wasm file and deploying it to the sandbox environment. +2. Writing tests -```rust - -#[tokio::test] -async fn test_nft_contract() -> anyhow::Result<()> { - let worker = workspaces::sandbox().await?; - let wasm = std::fs::read(NFT_WASM_FILEPATH)?; - let contract = worker.dev_deploy(&wasm).await?; -``` -Where -* `anyhow` - A crate that deals with error handling, making it more robust for developers. -* `worker` - Our gateway towards interacting with our sandbox environment. -* `contract`- The deployed contract on sandbox the developer interacts with. - ---- - -**Writing tests** - -Then you'll go directly into making a call into the contract, and initialize the NFT contract's metadata: +Following the `Worker` initialization, you'll go directly into making a call into the contract, and initialize the smart contract's metadata: ```rust let outcome = contract @@ -219,7 +232,6 @@ Then, you can view the minted NFT's metadata using a `view` call to `nft_metadat } ``` - @@ -524,8 +536,9 @@ async fn test_contract() -> anyhow::Result<()> { For a full Rust example, take a look at [examples/src/fast_forward.rs](https://github.com/near/workspaces-rs/blob/main/examples/src/fast_forward.rs). +## Examples -## Tutorials - -- Using Workspaces in [JavaScript](https://github.com/near/workspaces-js) -- Using Workspaces in [Rust](https://github.com/near/workspaces-rs) +| Language | Link | +|----------|------| +| JavaScript | https://github.com/near/workspaces-js | +| Rust | https://github.com/near/workspaces-rs | From eb60f4d3748036cc78f2a147c2ef288b58c354d9 Mon Sep 17 00:00:00 2001 From: Damian Parrino Date: Tue, 10 May 2022 20:31:46 -0300 Subject: [PATCH 5/6] remove tutorials --- docs/tutorials/contracts/workspaces/workspaces-js.md | 8 -------- docs/tutorials/contracts/workspaces/workspaces-rs.md | 8 -------- website/sidebars.json | 8 -------- 3 files changed, 24 deletions(-) delete mode 100644 docs/tutorials/contracts/workspaces/workspaces-js.md delete mode 100644 docs/tutorials/contracts/workspaces/workspaces-rs.md diff --git a/docs/tutorials/contracts/workspaces/workspaces-js.md b/docs/tutorials/contracts/workspaces/workspaces-js.md deleted file mode 100644 index 275bb5b8db2..00000000000 --- a/docs/tutorials/contracts/workspaces/workspaces-js.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -id: workspaces-js -title: NEAR Workspaces (TypeScript/JavaScript Edition) -sidebar_label: Workspaces JS ---- - - -Tutorial for Workspaces JS diff --git a/docs/tutorials/contracts/workspaces/workspaces-rs.md b/docs/tutorials/contracts/workspaces/workspaces-rs.md deleted file mode 100644 index c1dd42a0884..00000000000 --- a/docs/tutorials/contracts/workspaces/workspaces-rs.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -id: workspaces-rs -title: NEAR Workspaces (Rust Edition) -sidebar_label: Workspaces Rust ---- - -Tutorial for Workspaces Rust - diff --git a/website/sidebars.json b/website/sidebars.json index a725748790e..c43069f895c 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -150,14 +150,6 @@ "tutorials/contracts/xcc-rust", "tutorials/contracts/cross-contract-calls" ] - }, - { - "type": "category", - "label": "Workspaces", - "items": [ - "tutorials/contracts/workspaces/workspaces-js", - "tutorials/contracts/workspaces/workspaces-rs" - ] } ], "Front-End": [ From ef75bdf1b527aec6b684548624544c584ffdd7d5 Mon Sep 17 00:00:00 2001 From: Damian Parrino Date: Wed, 11 May 2022 23:31:34 -0300 Subject: [PATCH 6/6] examples --- docs/develop/contracts/workspaces.md | 35 ++++++++++++++++++++++++---- website/docusaurus.config.js | 4 ++-- website/package.json | 3 ++- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/docs/develop/contracts/workspaces.md b/docs/develop/contracts/workspaces.md index af171002d8f..1d1eecc2ffc 100644 --- a/docs/develop/contracts/workspaces.md +++ b/docs/develop/contracts/workspaces.md @@ -46,7 +46,11 @@ To get started with NEAR Workspaces you need to do two things: 1. Initialize a `Worker`. - A worker is the gateway towards interacting with your sandbox environment. 2. Write tests. - - See the JavaScript and Rust examples below: + - See the JavaScript and Rust examples below. + +:::tip +If you want to use NEAR Workspaces on Windows, please install the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install) (WSL). +::: ### Initializing a Worker @@ -538,7 +542,28 @@ For a full Rust example, take a look at [examples/src/fast_forward.rs](https://g ## Examples -| Language | Link | -|----------|------| -| JavaScript | https://github.com/near/workspaces-js | -| Rust | https://github.com/near/workspaces-rs | + + + + - [Workspaces JS examples](https://github.com/near/workspaces-js/tree/main/examples) + - [basic transactions](https://github.com/near/workspaces-js/blob/main/__tests__/01.basic-transactions.ava.ts) + - [patch state](https://github.com/near/workspaces-js/blob/main/__tests__/02.patch-state.ava.ts) + - [single use access keys with linkdrop](https://github.com/near/workspaces-js/blob/main/__tests__/03.single-use-access-keys-with-linkdrop.ava.ts) + - [cross contract calls with FT](https://github.com/near/workspaces-js/blob/main/__tests__/04.cross-contract-calls-with-fungible-token.ava.ts) + - [spoon contract to sandbox](https://github.com/near/workspaces-js/blob/main/__tests__/05.spoon-contract-to-sandbox.ava.ts) + - [init config](https://github.com/near/workspaces-js/blob/main/__tests__/06.init-config.ava.ts) + - [re-use worker](https://github.com/near/workspaces-js/blob/main/__tests__/07.resue-worker.ava.ts) + + + + + - [Workspaces examples](https://github.com/near/workspaces-rs/tree/main/examples) + - [croncat](https://github.com/near/workspaces-rs/blob/main/examples/src/croncat.rs) + - [fast forward](https://github.com/near/workspaces-rs/blob/main/examples/src/fast_forward.rs) + - [nft](https://github.com/near/workspaces-rs/blob/main/examples/src/nft.rs) + - [ref finance](https://github.com/near/workspaces-rs/blob/main/examples/src/ref_finance.rs) + - [spooning](https://github.com/near/workspaces-rs/blob/main/examples/src/spooning.rs) + - [status message](https://github.com/near/workspaces-rs/blob/main/examples/src/status_message.rs) + + + diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 2445bd62449..6db8060cedb 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -42,7 +42,7 @@ module.exports = { "showLastUpdateTime": true, "editUrl": "https://github.com/near/docs/edit/master/website", "path": "../docs", - "sidebarPath": "../website/sidebars.json" + "sidebarPath": "./sidebars.json" }, sitemap: { changefreq: 'weekly', @@ -54,7 +54,7 @@ module.exports = { }, "blog": {}, "theme": { - "customCss": "../src/css/customTheme.css" + "customCss": require.resolve("./src/css/customTheme.css") } } ] diff --git a/website/package.json b/website/package.json index 46bcb935037..2a76269b28d 100644 --- a/website/package.json +++ b/website/package.json @@ -32,6 +32,7 @@ "@saucelabs/theme-github-codeblock": "^0.1.1", "clsx": "^1.1.1", "react": "^17.0.1", - "react-dom": "^17.0.1" + "react-dom": "^17.0.1", + "url": "^0.11.0" } }