#contract #workspace #near #sandbox #workflow #smart-contracts #account

workspaces

自动化工作流程和测试 NEAR 智能合约的库

13 个版本 (7 个破坏性)

0.7.0 2022年11月30日
0.6.0 2022年10月6日
0.4.0 2022年7月21日
0.1.1 2022年1月20日
0.0.1 2021年11月5日

#1811 in 神奇豆

Download history 56/week @ 2024-04-15 231/week @ 2024-04-22 21/week @ 2024-04-29 48/week @ 2024-05-06 49/week @ 2024-05-13 51/week @ 2024-05-20 62/week @ 2024-05-27 39/week @ 2024-06-03 24/week @ 2024-06-10 26/week @ 2024-06-17 74/week @ 2024-06-24 7/week @ 2024-07-01 23/week @ 2024-07-08 23/week @ 2024-07-15 53/week @ 2024-07-22 51/week @ 2024-07-29

150 每月下载量
用于 5 crates

MIT/Apache

175KB
3.5K SLoC

NEAR Workspaces (Rust 版本)

Rust 库,用于自动化工作流程和编写 NEAR 智能合约的测试。该软件尚未最终确定,可能会发生变化。

Crates.io version Download Reference Documentation

发布说明

发布说明和未发布的更改可以在 CHANGELOG 中找到

要求

  • Rust v1.60.0 及以上版本
  • MacOS (x86 和 M1) 或 Linux (x86) 用于沙盒测试。

不支持 WASM 编译

workspaces-rs,该库本身,目前无法编译到 WASM。如果我们尝试运行这个库,同时还有其他已经编译到 WASM 的东西,比如 near-sdk-rs,最好将这个依赖项放在 Cargo.toml[dev-dependencies] 部分。

简单测试用例

一个简单的测试用例,让我们开始熟悉 workspaces 框架。在这里,我们将通过 NFT 合约了解如何使用 workspaces-rs 进行测试。

设置 — 导入

首先,我们需要声明一些方便的导入。

// 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;

我们需要提前准备好预编译的 WASM 合约并知道其路径。有关这些路径的位置,请参考相应的 near-sdk-{rs, js} 仓库/语言。

在本示例中,我们将指向示例 NFT 合约

const NFT_WASM_FILEPATH: &str = "./examples/res/non_fungible_token.wasm";

注意:有一个不稳定的特性可以让我们在测试时也能编译我们的项目。请参阅功能部分测试时编译合约

设置 -- 设置沙盒和部署NFT合约

这包括启动我们的沙盒,加载我们的wasm文件,并将该wasm文件部署到沙盒环境中。


#[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?;

位置

  • anyhow - 一个处理错误处理的crate,使开发人员的工作更稳健。
  • worker - 我们与沙盒环境交互的网关。
  • contract- 开发者与之交互的沙盒上部署的合约。

初始化合约 & 测试输出

然后我们将直接调用合约,并初始化NFT合约的元数据

    let outcome = contract
        .call("new_default_meta")
        .args_json(json!({
            "owner_id": contract.id(),
        }))
        .transact()  // note: we use the contract's keys here to sign the transaction
        .await?;

    // outcome contains data like logs, receipts and transaction outcomes.
    println!("new_default_meta outcome: {:#?}", outcome);

之后,让我们通过nft_mint来铸造一个NFT。这展示了我们可以提供的额外参数,例如存款和gas

    let deposit = 10000000000000000000000;
    let outcome = contract
        .call("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);

然后稍后,我们可以通过调用viewnft_metadata来查看我们铸造的NFT的元数据

    let result: serde_json::Value = contract
        .call("nft_metadata")
        .view()
        .await?
        .json()?;

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

更新合约之后

请注意,如果我们的合约代码发生变化,workspaces-rs对此不采取任何行动,因为我们正在使用deploy/dev_deploy将合约字节发送到网络。所以如果它确实改变了,我们将像往常一样重新编译合约,并将deploy/dev_deploy再次指向正确的WASM文件。然而,有一个workspaces功能可以为我们重新编译合约更改:请参阅实验性/不稳定的功能compile_project,告诉workspaces为我们编译一个Rust项目。

示例

更多独立示例可以在examples/src/*.rs中找到。

要运行上述NFT示例,请执行

cargo run --example nft

功能

选择网络

#[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?;
}

辅助函数

需要使用合约创建辅助函数?只需导入它并在周围传递

use workspaces::Contract;

// Helper function that calls into a contract we give it
async fn call_my_func(contract: &Contract) -> anyhow::Result<()> {
    // Call into the function `contract_function` with args:
    contract.call("contract_function")
        .args_json(serde_json::json!({
            "message": msg,
        })
        .transact()
        .await?;
    Ok(())
}

或者传递网络无关的工人

use workspaces::{DevNetwork, Worker};

const CONTRACT_BYTES: &[u8] = include_bytes!("./relative/path/to/file.wasm");

// Create a helper function that deploys a specific contract
// NOTE: `dev_deploy` is only available on `DevNetwork`s such as sandbox and testnet.
async fn deploy_my_contract(worker: Worker<impl DevNetwork>) -> anyhow::Result<Contract> {
    worker.dev_deploy(CONTRACT_BYTES).await
}

查看账户详情

我们可以这样检查我们的账户余额

#[test(tokio::test)]
async fn test_contract_transfer() -> anyhow::Result<()> {
    let transfer_amount = near_units::parse_near!("0.1");
    let worker = workspaces::sandbox().await?;

    let contract = worker
        .dev_deploy(include_bytes!("../target/res/your_project_name.wasm"))
        .await?;
    contract.call("new")
        .max_gas()
        .transact()
        .await?;

    let alice = worker.dev_create_account().await?;
    let bob = worker.dev_create_account().await?;
    let bob_original_balance = bob.view_account().await?.balance;

    alice.call(contract.id(), "function_that_transfers")
        .args_json(json!({ "destination_account": bob.id() }))
        .max_gas()
        .deposit(transfer_amount)
        .transact()
        .await?;
    assert_eq!(
        bob.view_account().await?.balance,
        bob_original_balance + transfer_amount
    );

    Ok(())
}

要查看其他链相关详细信息,请参阅WorkerAccountContract的文档

spooning - 从主网/测试网拉取现有状态和合约

此示例将展示如何将测试网合约的状态拉入我们的本地沙盒环境。

我们首先进行常规导入

use near_units::{parse_gas, parse_near};
use workspaces::network::Sandbox;
use workspaces::{Account, AccountId, BlockHeight, Contract, Worker};

然后指定我们想要从测试网拉取的合约名称

const CONTRACT_ACCOUNT: &str = "contract_account_name_on_testnet.testnet";

让我们也指定一个特定的区块ID,将其引用回特定时间。以防我们的合约或我们要引用的合约已更改或更新

const BLOCK_HEIGHT: BlockHeight = 12345;

创建一个名为pull_contract的函数,它将从链上拉取合约的.wasm文件,并将其部署到我们的本地沙盒。我们将用所有数据重新初始化它以运行测试。

async fn pull_contract(owner: &Account, worker: &Worker<Sandbox>) -> anyhow::Result<Contract> {
    let testnet = workspaces::testnet_archival().await?;
    let contract_id: AccountId = CONTRACT_ACCOUNT.parse()?;

此行实际上将从测试网拉取相关合约,并在其上设置初始余额为1000 NEAR。

在此之后,我们必须再次用我们的元数据初始化合约。这是因为合约的数据太大,无法由RPC服务拉取,其限制设置为50kb。


    let contract = worker
        .import_contract(&contract_id, &testnet)
        .initial_balance(parse_near!("1000 N"))
        .block_height(BLOCK_HEIGHT)
        .transact()
        .await?;

    owner
        .call(contract.id(), "init_method_name")
        .args_json(serde_json::json!({
            "arg1": value1,
            "arg2": value2,
        }))
        .transact()
        .await?;

    Ok(contract)
}

时间旅行

workspaces 测试支持将区块链状态向前转发。这意味着需要时间敏感数据的合约无需等待沙盒中生成相同数量的区块。我们可以简单地调用 worker.fast_forward 来获取更远的时间

#[tokio::test]
async fn test_contract() -> anyhow::Result<()> {
    let worker = workspaces::sandbox().await?;
    let contract = worker.dev_deploy(WASM_BYTES).await?;

    let blocks_to_advance = 10000;
    worker.fast_forward(blocks_to_advance).await?;

    // Now, "do_something_with_time" will be in the future and can act on future time-related state.
    contract.call("do_something_with_time")
        .transact()
        .await?;
}

完整示例请参考 examples/src/fast_forward.rs

测试期间编译合约

注意,这是一个不稳定的功能,可能会发生变化。要启用它,请将 unstable 特性标志添加到 workspaces 依赖项中的 Cargo.toml

[dependencies]
workspaces = { version = "...", features = ["unstable"] }

然后,在我们的测试中,在我们调用 deploydev_deploy 之前,我们可以编译我们的项目

#[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).await?;
    ...
}

完整示例请参考 workspaces/tests/deploy_project.rs

环境变量

如果遇到问题,以下环境变量将很有用

  • NEAR_RPC_TIMEOUT_SECS:默认值为 10 秒,这是在沙盒或任何其他网络(如测试网)与 RPC 服务通信时超时等待的时间。
  • NEAR_SANDBOX_BIN_PATH:如果我们想使用非默认版本的沙盒或使用我们自己的自定义功能来配置 nearcore 进行测试,请将此设置为我们的预构建 neard-sandbox 二进制路径。

依赖项

~32–47MB
~783K SLoC