#contract #near #workflow #sandbox #smart-contracts #automating #environment

chaotic-tempest-testbed

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

1 个不稳定版本

0.0.1 2022 年 4 月 12 日

#13 in #automating

MIT/Apache

115KB
2K SLoC

NEAR 工作空间(Rust 版本)

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

Crates.io version Download Reference Documentation

发行说明

发行说明和未发布更改可在 CHANGELOG 中找到。

要求

  • Rust v1.56 及以上版本
  • MacOS (x86) 或 Linux (x86) 用于沙盒测试。测试网总是可用。

M1 MacOS

注意:由于与 wasmer 的内部升级,当前版本的 workspaces-rs 不支持在 M1 芯片设备上使用。M1 用户应使用 workspaces-rs 版本 0.1.1 直到此问题得到解决。在此处查看此问题的进展 此处

即使在上述注释中,我们也可以通过设置 rosetta 和我们的交叉编译目标在 M1 上使用 workspaces-rs 版本 0.1.1

softwareupdate --install-rosetta
rustup default stable-x86_64-apple-darwin

不支持 WASM 编译

workspaces-rs,该库本身,目前无法编译为 WASM。最好将其依赖项放入 [dev-dependencies] 部分,如果我们尝试运行该库与已经编译为 WASM 的某些内容(如 near-sdk-rs)一起使用。

简单测试案例

一个简单的测试,让我们开始熟悉 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;

// Additional convenient imports that allows workspaces to function readily.
use workspaces::prelude::*;

我们需要提前拥有预编译的 WASM 合约并知道其路径。在这个演示中,我们将指向示例的 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(&worker, "new_default_meta")
        .args_json(json!({
            "owner_id": contract.id(),
        }))?
        .transact()
        .await?;

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

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

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

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

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

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

示例

更多独立的示例可以在 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::prelude::*;
use workspaces::{Contract, DevNetwork, Network, Worker};

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

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

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

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

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

use near_units::{parse_gas, parse_near};
use workspaces::network::Sandbox;
use workspaces::prelude::*;
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();
    let contract_id: AccountId = CONTRACT_ACCOUNT.parse()?;

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

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


    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)
}

时间旅行

workspaces 测试提供将区块链状态向前推进的支持。这意味着需要时间敏感数据的合约不需要等待与沙盒上的区块生成相同的时间。我们可以简单地调用 worker.fast_forward 来推进时间

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

有关完整示例,请参阅 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);
    ...
}

有关完整示例,请参阅 workspaces/tests/deploy_project.rs

依赖关系

~32–48MB
~844K SLoC