#contract #near #sandbox #workspace #account #workflow #environment

near-workspaces

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

18 个版本 (破坏性)

新版本 0.12.0 2024 年 8 月 15 日
0.11.0 2024 年 7 月 8 日
0.10.0 2024 年 1 月 25 日
0.9.0 2023 年 10 月 30 日
0.0.1 2021 年 11 月 5 日

#1171 in 神奇豆

Download history 620/week @ 2024-04-24 560/week @ 2024-05-01 517/week @ 2024-05-08 631/week @ 2024-05-15 651/week @ 2024-05-22 685/week @ 2024-05-29 526/week @ 2024-06-05 531/week @ 2024-06-12 474/week @ 2024-06-19 820/week @ 2024-06-26 665/week @ 2024-07-03 516/week @ 2024-07-10 591/week @ 2024-07-17 641/week @ 2024-07-24 1415/week @ 2024-07-31 1319/week @ 2024-08-07

每月下载量 4,048
用于 4 crates

MIT/Apache

235KB
4.5K SLoC

NEAR 工作区 (Rust 版本)

Rust 库,用于自动化工作流和编写 NEAR 智能合约的测试。此软件尚未完成,可能会发生变化。

Crates.io version Download Reference Documentation

版本说明

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

要求

  • Rust v1.69.0 和更高版本。
  • MacOS (x86 和 M1) 或 Linux (x86) 用于沙盒测试。

不支持 WASM 编译

near-workspaces-rs,库本身,目前无法编译为 WASM。如果尝试与已编译为 WASM 的库(如 near-sdk-rs)一起运行此库,最好将其依赖项放在 Cargo.toml[dev-dependencies] 部分。

简单测试用例

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

设置 — 导入

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

// macro allowing us to convert args into JSON bytes to be read by the contract.
use serde_json::json;

我们需要提前准备好预编译的WASM合约并知道其路径。请参考相应近端SDK-{rs, js}仓库/语言的路径。

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

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

注意:有一个不稳定的功能允许我们在测试期间编译我们的项目。请参阅功能部分 在测试期间编译合约

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

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


#[tokio::test]
async fn test_nft_contract() -> anyhow::Result<()> {
    let worker = near_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。这展示了我们可以提供的额外参数,例如存款和燃气。

    use near_gas::NearGas;
    use near_workspaces::types::NearToken;

    let deposit = NearToken::from_near(100);
    let outcome = contract
        .call("nft_mint")
        .args_json(json!({
            "token_id": "0",
            "token_owner_id": contract.id(),
            "token_metadata": {
                "title": "Olympus Mons",
                "description": "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(NearGas::from_tgas(300))
        .transact()
        .await?;

    println!("nft_mint outcome: {:#?}", outcome);

然后我们可以通过调用 view 进入 nft_metadata 来查看我们铸造的NFT的元数据

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

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

之后更新合约

注意,如果我们的合约代码发生变化,由于我们使用 deploy/dev_deploy 仅将合约字节发送到网络,所以 near-workspaces-rs 对此不采取任何行动。因此,如果它发生变化,我们将必须像往常一样重新编译合约,并再次将 deploy/dev_deploy 指向正确的WASM文件。然而,有一个功能可以为我们重新编译合约更改:请参阅实验性/不稳定 compile_project 函数,以告知 near-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 = near_workspaces::sandbox().await?;
    // or for testnet:
    let worker = near_workspaces::testnet().await?;
}

辅助函数

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

use near_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 near_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 = NearToken::from_millinear(100);
    let worker = near_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 的文档

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

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

我们首先从通常的导入开始

use near_workspaces::network::Sandbox;
use near_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 = near_workspaces::testnet_archival().await?;
    let contract_id: AccountId = CONTRACT_ACCOUNT.parse()?;

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

接下来,我们需要再次使用我们自己的元数据初始化合约。这是因为合约的数据太大,无法通过RPC服务下载,其限制为50kb。


    use near_workspaces::types::NearToken;
    let contract = worker
        .import_contract(&contract_id, &testnet)
        .initial_balance(NearToken::from_near(1000))
        .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 = near_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

测试期间编译合约

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

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

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

#[tokio::test]
async fn test_contract() -> anyhow::Result<()> {
    let wasm = near_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

WASM可执行文件覆盖率分析

生成的代码覆盖率报告有助于识别测试期间执行的代码区域,因此它是一个确保合约可靠性和质量的有价值工具。有关实现此功能的步骤指南文档 在这里

项目可以在以下位置找到: https://github.com/hknio/wasmcov

其他功能

其他功能可以直接在 examples/ 文件夹中找到,其中一些文档说明了如何使用它们。

环境变量

如果有任何问题,以下环境变量将非常有用

  • NEAR_RPC_TIMEOUT_SECS:默认为10秒,但这是在与沙盒或任何其他网络(如测试网)通信时等待RPC服务超时之前的时间。
  • NEAR_SANDBOX_BIN_PATH:如果我们想使用非默认版本的沙盒或配置 nearcore 以测试我们想在 near-workspaces 中测试的自定义功能,请将此设置为我们的预构建 neard-sandbox bin 路径。
  • NEAR_SANDBOX_MAX_PAYLOAD_SIZE:设置向沙盒发送事务提交的最大有效载荷大小。默认为1gb,这对于修补大型状态是必要的。
  • NEAR_SANDBOX_MAX_FILES:设置沙盒中一次可以打开的最大文件数。如果没有指定,则默认大小为4096。实际近链将使用超过10,000,但在测试中这应该要低得多,因为我们没有持续运行的区块链,除非我们的测试需要那么长时间。
  • NEAR_RPC_API_KEY:这是与RPC节点通信所需的API密钥。这对于与Pagoda控制台或可以访问RPC指标的服务交互非常有用。这不是一个 强制 要求,但建议在示例文件夹中运行Pagoda示例时使用。
  • NEAR_ENABLE_SANDBOX_LOG:将此设置为 1 以启用沙盒日志记录。这对于调试与 neard-sandbox 二进制文件相关的问题非常有用。

依赖项

~64–85MB
~1.5M SLoC