Skip to content

Commit

Permalink
release: 0.2 (#104)
Browse files Browse the repository at this point in the history
* Bump sandbox to 0.2 crate

* Added changelog

* Bump version up to 0.2

* Update README w/ changelog and note about compiling

* Update CHANGELOG.md

Co-authored-by: Austin Abell <austinabell8@gmail.com>

* Added changelog entry + revert back to optional dep

* Fix cargo

* Updated README with more examples and details

* Update CHANGELOG with new sections

* Update workspaces/Cargo.toml

Co-authored-by: Austin Abell <austinabell8@gmail.com>

* Minor fix in CHANGELOG wording

* README fixes

Co-authored-by: Austin Abell <austinabell8@gmail.com>
  • Loading branch information
ChaoticTempest and austinabell committed Apr 6, 2022
1 parent b45f4b5 commit f2dcf76
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 25 deletions.
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Changelog

## [unreleased]

## [0.2.0] - 2022-04-05

### Added
- Time-traveling - the ability to go forwards in block height within tests. This allows to test time specific data changing within contracts: https://github.com/near/workspaces-rs/pull/73
- Credentials created from account/contract creation are now allowed to be stored and specified by users. https://github.com/near/workspaces-rs/pull/98
- [Unstable] Allow users to compile contract projects within tests without having to manually go through this step. https://github.com/near/workspaces-rs/pull/77
- Batch transactions or transactions with multiple actions are now possible. https://github.com/near/workspaces-rs/pull/72
- Sandbox node (nearcore binary) logs are now suppressed and can be re-enabled if desired. https://github.com/near/workspaces-rs/pull/85
- Results now expose logs, receipts, and transaction outcome values. https://github.com/near/workspaces-rs/pull/70
- Convenience methods `Worker::view_code`, `Worker::view_latest_block`, `Worker::view_account`, `Account::view_account`, `Contract::view_account`, `Contract::view_code` now available. https://github.com/near/workspaces-rs/pull/82
- Improve error handling. If a transaction fails, this error will now be apart of the `Result` return initially. https://github.com/near/workspaces-rs/pull/83
- Added `tracing` logging to internal code and examples. https://github.com/near/workspaces-rs/pull/55 and https://github.com/near/workspaces-rs/pull/75
- Convenient `CallExecutionDetails::{is_success, is_failure}` for testing outcomes of transactions. https://github.com/near/workspaces-rs/pull/58
- Added `mainnet_archival` and `testnet_archival`, where `ref-finance` example now uses `mainnet_archival`. https://github.com/near/workspaces-rs/pull/57 and https://github.com/near/workspaces-rs/pull/94


### Changed
- key type for `patch_state` now a slice and no longer require `StoreKey`. https://github.com/near/workspaces-rs/pull/109
- Reorganized imports internally for better maintainability. https://github.com/near/workspaces-rs/pull/102
- No longer running into non-deterministic query failures if RPC isn't available, but this is a breaking API. All `workspaces::{sandbox, testnet, mainnet}` now require `.await?` at the end. https://github.com/near/workspaces-rs/pull/99
- TLA trait no longer apart of all networks -- only dev-networks (sandbox, testnet). https://github.com/near/workspaces-rs/pull/101
- Retry times have now been shorten and should take a maximum of 1 second. https://github.com/near/workspaces-rs/pull/92
- doc builds on [docs.rs](https://docs.rs) has now been fixed. https://github.com/near/workspaces-rs/pull/90
- `patch_state` now takes in slices. https://github.com/near/workspaces-rs/pull/80 and https://github.com/near/workspaces-rs/pull/79
- Make `access_key` call do optimistic queries which led to better retry times. https://github.com/near/workspaces-rs/pull/60
- Functions no longer take in owned but referenced `AccountId`s now. https://github.com/near/workspaces-rs/pull/52

### Removed
- Empty JSON array is no longer a valid default argument supplied to transactions. Recommended to supply empty `{}` in the case of JSON if all function arguments in the contract are optional types. https://github.com/near/workspaces-rs/pull/84

## [0.1.1] - 2021-01-24

### Changed
- Fix race condition when installing sandbox and running multiples tests at the same time. https://github.com/near/workspaces-rs/pull/46


[Unreleased]: https://github.com/near/workspaces-rs/compare/0.2.0...HEAD
[0.2.0]: https://github.com/near/workspaces-rs/compare/0.1.1...0.2.0
[0.1.1]: https://github.com/near/workspaces-rs/compare/0.1.0...0.1.1
[0.1.0]: https://github.com/near/workspaces-rs/releases/tag/0.1.0
218 changes: 199 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<h1>NEAR Workspaces (Rust Edition)</h1>

<p>
<strong>Rust library for automating workflows and writing tests for NEAR smart contracts. This software is in early alpha (use at your own risk)</strong>
<strong>Rust library for automating workflows and writing tests for NEAR smart contracts. This software is not final, and will likely change.</strong>
</p>

<p>
Expand All @@ -13,50 +13,135 @@
</p>
</div>

## Release notes
**Release notes and unreleased changes can be found in the [CHANGELOG](CHANGELOG.md)**

## Requirements
- rust v1.56 and up
- MacOS (x86), M1 (through rosetta) or Linux (x86) for sandbox tests. Testnet is available regardless
- 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).

### M1 MacOS Setup
To be able to use this library on an M1 Mac, we would need to setup rosetta plus our cross compile target:
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
```

## Testing
A simple test to get us going and familiar with the features:
### Note about compiling with NEAR Contracts
`workspaces-rs`, the library itself, does not currently compile to WASM. So, if we were compiling it alongside a `wasm32` target, such as compiling alongside a NEAR contract:
```toml
# Cargo.toml
[dependencies]
workspaces = "*"
near-sdk = "*"
```
It would throw an error when we run `cargo build --target wasm32-unknown-unknown`. Instead, we should add `workspaces-rs` into `[dev-dependencies]` for testing contracts:
```toml
# Cargo.toml
[dependencies]
near-sdk = "*"

[dev-dependencies]
workspaces = "*"
```

## 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
#![cfg(test)]
// 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_deploy_and_view() -> anyhow::Result<()> {
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.

let contract = worker.dev_deploy(include_bytes!("path/to/file.wasm"))
.await
.expect("could not dev-deploy contract");
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?;

let result: String = contract.call(&worker, "function_name")
.args_json(serde_json::json!({
"some_arg": "some_value",
// 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()?;

assert_eq!(result, "OUR_EXPECTED_RESULT");
println!("--------------\n{}", result);
println!("Dev Account ID: {}", contract.id());
Ok(())
}
```

## Examples
Some examples can be found in `examples/src/*.rs` to run it standalone.
More standalone examples can be found in `examples/src/*.rs`.

To run the NFT example, execute:
To run the above NFT example, execute:
```
cargo run --example nft
```
Expand All @@ -66,7 +151,7 @@ cargo run --example nft
### Choosing a network

```rust
#[tokio::main] # or whatever runtime we want
#[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
Expand Down Expand Up @@ -102,3 +187,98 @@ async fn deploy_my_contract(worker: Worker<impl DevNetwork>) -> anyhow::Result<C
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<Sandbox>) -> anyhow::Result<Contract> {
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).
8 changes: 4 additions & 4 deletions workspaces/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "workspaces"
version = "0.1.1"
version = "0.2.0"
edition = "2018"
license = "MIT OR Apache-2.0"
readme = "README.md"
Expand All @@ -11,7 +11,7 @@ Library for automating workflows and testing NEAR smart contracts.

[dependencies]
async-trait = "0.1"
async-process = { version = "1.3.0", optional = true }
async-process = { version = "1.3", optional = true }
anyhow = "1.0"
base64 = "0.13"
borsh = "0.9"
Expand All @@ -34,10 +34,10 @@ near-crypto = "0.12.0"
near-primitives = "0.12.0"
near-jsonrpc-primitives = "0.12.0"
near-jsonrpc-client = { version = "0.3.0", features = ["sandbox"] }
near-sandbox-utils = "0.1.2"
near-sandbox-utils = "0.2.0"

[build-dependencies]
near-sandbox-utils = "0.1.2"
near-sandbox-utils = "0.2.0"

[target.'cfg(unix)'.dependencies]
libc = "0.2"
Expand Down
8 changes: 6 additions & 2 deletions workspaces/src/network/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ use crate::network::Sandbox;
use portpicker::pick_unused_port;
use tracing::info;

// TODO: swap over the async version of this in the future. Won't be a breaking API
// since we already have async marked in the functions that we are exposing.
use near_sandbox_utils::sync as sandbox;

pub struct SandboxServer {
pub(crate) rpc_port: u16,
pub(crate) net_port: u16,
Expand All @@ -29,9 +33,9 @@ impl SandboxServer {

// Remove dir if it already exists:
let _ = std::fs::remove_dir_all(&home_dir);
near_sandbox_utils::init(&home_dir)?.wait()?;
sandbox::init(&home_dir)?.wait()?;

let child = near_sandbox_utils::run(&home_dir, self.rpc_port, self.net_port)?;
let child = sandbox::run(&home_dir, self.rpc_port, self.net_port)?;
info!(target: "workspaces", "Started sandbox: pid={:?}", child.id());
self.process = Some(child);

Expand Down

0 comments on commit f2dcf76

Please sign in to comment.