#contract #unc #workspace #sandbox #workflow #account #automating

unc-workspaces

自动化工作流程和测试 UNC 智能合同的库

7 个版本

0.8.3 2024 年 4 月 12 日
0.8.2 2024 年 4 月 11 日
0.7.3 2024 年 4 月 9 日
0.1.0 2024 年 3 月 7 日

#2221 in 魔法豆

Download history 239/week @ 2024-04-10 5/week @ 2024-04-17 2/week @ 2024-05-15 17/week @ 2024-05-22

325 次每月下载

GPL-2.0-or-later

225KB
4.5K SLoC

UNC 工作空间 (Rust 版本)

Rust 库,用于自动化工作流程和编写 UNC 智能合同的测试。该软件不是最终版本,可能会发生变化。

Crates.io version Download Reference Documentation

发布说明

发布说明和未发布变更可以在 CHANGELOG 中找到。

需求

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

不支持 WASM 编译

unc-workspaces-rs,该库本身,目前不支持编译为 WASM。如果我们要运行此库并同时运行其他编译为 WASM 的库,例如 unc-sdk-rs,最好将其依赖项放入 Cargo.toml 文件的 [dev-dependencies] 部分中。

简单测试案例

一个简单的测试,让我们开始并熟悉 unc-workspaces 框架。在这里,我们将探讨 NFT 合同以及如何使用 unc-workspaces-rs 进行测试。

设置 -- 导入

首先,我们需要声明一些导入以提高方便性。

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

我们需要提前准备好预编译的 WASM 合同及其路径。有关这些路径的位置,请参阅相应的 unc-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 = unc_workspaces::sandbox().await?;
    let wasm = std::fs::read(NFT_WASM_FILEPATH)?;
    let contract = worker.dev_deploy(&wasm).await?;

位置

  • anyhow - 一个处理错误处理的包,使开发者工作更稳健。
  • 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 unc_gas::UncGas;
    use unc_workspaces::types::UncToken;

    let deposit = UncToken::from_unc(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(UncGas::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 只是将合约字节发送到网络,所以 unc-workspaces-rs 对此不采取任何行动。因此,如果它发生变化,我们必须像往常一样重新编译合约,并将 deploy/dev_deploy 再次指向正确的 WASM 文件。然而,有一个特性会为我们重新编译合约更改:请参阅实验性的/不稳定的 compile_project 函数,以告诉 unc-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 = unc_workspaces::sandbox().await?;
    // or for testnet:
    let worker = unc_workspaces::testnet().await?;
}

辅助函数

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

use unc_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 unc_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 = UncToken::from_milliunc(100);
    let worker = unc_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 unc_workspaces::network::Sandbox;
use unc_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 = unc_workspaces::testnet_archival().await?;
    let contract_id: AccountId = CONTRACT_ACCOUNT.parse()?;

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

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


    use unc_workspaces::types::UncToken;
    let contract = worker
        .import_contract(&contract_id, &testnet)
        .initial_balance(UncToken::from_unc(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 = unc_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]
unc-workspaces = { version = "...", features = ["unstable"] }

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

#[tokio::test]
async fn test_contract() -> anyhow::Result<()> {
    let wasm = unc_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/ 文件夹中找到,其中一些文档概述了如何使用它们。

环境变量

如果遇到任何问题,这些环境变量将非常有用

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

依赖项

~59–80MB
~1.5M SLoC