#contract #workspace #sandbox #utility #workflow #account #automating

utility-workspaces

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

5 个版本

0.12.4 2024 年 5 月 30 日
0.12.3 2024 年 5 月 29 日
0.12.2 2024 年 5 月 29 日
0.9.1 2024 年 5 月 24 日
0.9.0 2024 年 5 月 22 日

#7 in #automating

Download history 122/week @ 2024-05-17 610/week @ 2024-05-24 167/week @ 2024-05-31 34/week @ 2024-06-07 24/week @ 2024-06-14 1/week @ 2024-06-21 25/week @ 2024-06-28 81/week @ 2024-07-05 2/week @ 2024-07-12 79/week @ 2024-07-26 3/week @ 2024-08-02

82 每月下载次数

GPL-2.0-or-later

225KB
4.5K SLoC

Utility Workspaces (Rust 版本)

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

Crates.io version Download Reference Documentation

版本说明

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

需求

  • Rust v1.78.0 及以上。
  • MacOS (x86 和 M1/2) 或 Linux (x86) 用于沙箱测试。

不支持 WASM 编译

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

简单测试用例

一个简单的测试用例,让我们开始熟悉 utility-workspaces 框架。在这里,我们将通过 NFT 合约了解如何使用 utility-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 文件并将该文件部署到沙箱环境。


#[tokio::test]
async fn test_nft_contract() -> anyhow::Result<()> {
    let worker = utility_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

    use unc_gas::UncGas;
    use utility_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 仅将合约字节发送到网络,因此 utility-workspaces-rs 对此不做任何处理。因此,如果它确实发生了变化,我们将不得不像往常一样重新编译合约,并将 deploy/dev_deploy 再次指向正确的WASM文件。然而,有一个功能可以为我们重新编译合约更改:请参阅实验性/不稳定 compile_project 函数,用于告诉utility-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 = utility_workspaces::sandbox().await?;
    // or for testnet:
    let worker = utility_workspaces::testnet().await?;
}

辅助函数

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

use utility_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 utility_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 = utility_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 的文档

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

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

我们首先开始使用通常的导入

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

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

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


    use utility_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 = utility_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]
utility-workspaces = { version = "...", features = ["unstable"] }

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

#[tokio::test]
async fn test_contract() -> anyhow::Result<()> {
    let wasm = utility_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:如果我们想使用非默认版本的沙箱或配置带有我们自己的自定义功能(我们想在 utility-workspaces 中测试的)的 unccore,请将此设置为我们的预构建 unc-node-sandbox bin 路径。
  • UNC_SANDBOX_MAX_PAYLOAD_SIZE:设置发送交易提交到沙箱的最大有效负载大小。默认为 1gb,对于修补大型状态是必要的。
  • UNC_SANDBOX_MAX_FILES:设置沙箱中一次可以打开的最大文件数量。如果没有指定,将使用默认的 4096。实际 unc 链将使用超过 10,000,但在测试中应该低得多,因为我们没有始终运行的区块链,除非我们的测试花费了这么多时间。
  • UNC_RPC_API_KEY:这是与 RPC 节点通信所需的 API 密钥。这对于与控制台或可以访问 RPC 指标的服务等交互非常有用。这不是 要求,但建议在 examples 文件夹中的示例中运行它。
  • UNC_ENABLE_SANDBOX_LOG:将此设置为 1 以启用沙箱日志记录。这对于调试与 unc-node-sandbox 二进制文件相关的问题非常有用。

依赖项

~59–83MB
~1.5M SLoC