#provenance #smart-contracts #blockchain #defi #finance #integration-tests #api-bindings

provwasm-test-tube

用于构建 Provenance 区块链智能合约集成测试环境的库

1 个不稳定版本

0.1.0-rc12024年4月29日

#7 in #defi

Apache-2.0

145KB
3K SLoC

Rust 2.5K SLoC // 0.0% comments Go 593 SLoC // 0.1% comments

provwasm-test-tube

provwasm-test-tube on crates.io Docs

一个 CosmWasm x ProvWasm 集成测试库,与 cw-multi-test 不同,它允许您将您的 ProvWasm 合同与真实链的逻辑进行测试,而不是使用模拟。

目录

兼容性

provwasm-test-tube provwasm provenance
0.1.0 2.2.0 1.18.0

入门指南

为了演示 provwasm-test-tube 的工作原理,让我们使用一个简单的示例合同: marker

以下是设置测试的方法

use cosmwasm_std::Coin;
use provwasm_test_tube::{ProvwasmTestApp, RunnerError};

#[test]
fn test() -> Result<(), RunnerError> {
    // create new provenance appchain instance.
    let app = ProvwasmTestApp::new();

    // create new account with initial funds
    let accs = app.init_accounts(&[Coin::new(1_000_000_000_000, "nhash")], 2)?;

    let admin = &accs[0];
    let new_admin = &accs[1];

    Ok(())
}

现在我们有了 appchain 实例和账户,它们具有一些初始余额,并且可以与 appchain 交互。这不需要运行 Docker 实例或外部进程,它只是将 appchain 的代码作为库加载,并创建一个内存实例。

请注意,init_accounts 是一个便利函数,用于创建具有相同初始余额的多个账户。如果您只想创建一个账户,可以使用 init_account 代替。

use cosmwasm_std::Coin;
use provwasm_test_tube::{ProvwasmTestApp, RunnerError};

fn test() -> Result<(), RunnerError> {
    let app = ProvwasmTestApp::new();

    let account = app.init_account(&[Coin::new(1_000_000_000_000, "nhash")])?;

    Ok(())
}

现在,如果我们想测试一个 provwasm 合同,我们需要

  • 构建 wasm 文件
  • 存储代码
  • 实例化

然后我们可以开始与我们的合同交互

use cosmwasm_std::Coin;
use provwasm_test_tube::wasm::Wasm;
use provwasm_test_tube::{Module, ProvwasmTestApp, RunnerError};

fn test() -> Result<(), RunnerError> {
    let app = ProvwasmTestApp::default();
    let accs = app.init_accounts(&[Coin::new(100_000_000_000_000, "nhash")], 1)?;
    let admin = &accs[0];

    let wasm = Wasm::new(&app);
    let wasm_byte_code = std::fs::read("../contracts/marker/artifacts/marker.wasm").unwrap();
    let store_res = wasm.store_code(&wasm_byte_code, None, admin);
    let code_id = store_res?.data.code_id;

    Ok(())
}

现在让我们执行合同并验证合同的状态是否已正确更新。

use cosmwasm_std::{Coin, Uint128};
use provwasm_test_tube::wasm::Wasm;
use provwasm_test_tube::{Account, Module, ProvwasmTestApp, RunnerError};

use marker::msg::{ExecuteMsg, InitMsg, QueryMsg};
use marker::types::Marker;

#[test]
fn create_and_withdraw() -> Result<(), RunnerError> {
    let app = ProvwasmTestApp::default();
    let accs = app.init_accounts(&[Coin::new(100_000_000_000_000, "nhash")], 1)?;
    let admin = &accs[0];

    let wasm = Wasm::new(&app);
    let wasm_byte_code = std::fs::read("../contracts/marker/artifacts/marker.wasm").unwrap();
    let store_res = wasm.store_code(&wasm_byte_code, None, admin);
    let code_id = store_res?.data.code_id;
    assert_eq!(code_id, 1);

    // let init_admins = vec![admin.address()];
    let contract_addr = wasm
        .instantiate(
            code_id,
            &InitMsg {
                name: "marker-test.sc.pb".to_string(),
            },
            Some(&admin.address()),
            None,
            &[],
            admin,
        )?
        .data
        .address;

    wasm.execute::<ExecuteMsg>(
        &contract_addr,
        &ExecuteMsg::Create {
            supply: Uint128::new(100),
            denom: "spy".into(),
            allow_forced_transfer: false,
        },
        &[],
        admin,
    )?;

    wasm.execute::<ExecuteMsg>(
        &contract_addr,
        &ExecuteMsg::GrantAccess {
            denom: "spy".into(),
        },
        &[],
        admin,
    )?;

    wasm.execute::<ExecuteMsg>(
        &contract_addr,
        &ExecuteMsg::Finalize {
            denom: "spy".into(),
        },
        &[],
        admin,
    )?;

    wasm.execute::<ExecuteMsg>(
        &contract_addr,
        &ExecuteMsg::Activate {
            denom: "spy".into(),
        },
        &[],
        admin,
    )?;

    wasm.execute::<ExecuteMsg>(
        &contract_addr,
        &ExecuteMsg::Withdraw {
            amount: Uint128::new(20),
            denom: "spy".into(),
        },
        &[],
        admin,
    )?;

    let marker = wasm.query::<QueryMsg, Marker>(
        &contract_addr,
        &QueryMsg::GetByDenom {
            denom: "spy".into(),
        },
    )?;

    assert_eq!(marker.marker_account.denom, "spy");

    Ok(())
}

调试

在您的合同代码中,如果您想调试,可以使用 deps.api.debug(..),它将调试消息打印到 stdout。 wasmd 默认禁用了此功能,但 ProvwasmTestApp 允许 stdout 输出,以便您可以在运行测试的同时调试智能合约。

使用模块包装器

在某些情况下,您可能需要直接与 appchain 逻辑交互以设置环境或查询 appchain 的状态。模块包装器提供了方便的函数来与 appchain 的模块交互。

让我们尝试与 Marker 模块交互

use std::convert::TryFrom;

use cosmwasm_std::{Coin, Uint128};
use provwasm_test_tube::{Account, Module, ProvwasmTestApp, RunnerError};

use provwasm_std::types::provenance::marker::v1::{
    Access, AccessGrant, MarkerAccount, MarkerStatus, MarkerType, MsgAddMarkerRequest,
    QueryMarkerRequest,
};

#[test]
fn create_and_withdraw() -> Result<(), RunnerError> {
    let app = ProvwasmTestApp::default();
    let accs = app.init_accounts(&[Coin::new(100_000_000_000_000, "nhash")], 1)?;
    let admin = &accs[0];

    let marker_module = provwasm_test_tube::marker::Marker::new(&app);
    marker_module.add_marker(
        MsgAddMarkerRequest {
            amount: Some(
                Coin {
                    amount: Uint128::new(100),
                    denom: "spy".to_string(),
                }
                .into(),
            ),
            manager: admin.address(),
            from_address: admin.address(),
            status: MarkerStatus::Proposed.into(),
            marker_type: MarkerType::Coin.into(),
            access_list: vec![AccessGrant {
                address: admin.address(),
                permissions: vec![
                    Access::Admin.into(),
                    Access::Burn.into(),
                    Access::Deposit.into(),
                    Access::Delete.into(),
                    Access::Mint.into(),
                    Access::Withdraw.into(),
                ],
            }],
            supply_fixed: false,
            allow_governance_control: false,
            allow_forced_transfer: false,
            required_attributes: vec![],
            usd_cents: 0,
            volume: 0,
            usd_mills: 0,
        },
        admin,
    )?;

    let marker_response = marker_module.query_marker(&QueryMarkerRequest {
        id: "spy".to_string(),
    })?;

    assert_eq!(
        MarkerAccount::try_from(marker_response.marker.unwrap())
            .unwrap()
            .denom,
        "spy"
    );

    Ok(())
}

自定义模块包装器

您可能找不到想要使用的包装器,或者提供的包装器过于冗长。好消息是,创建自己的包装器非常简单。

以下是作为库用户如何重新定义Hold模块包装器的方法

use provwasm_test_tube::{fn_execute, fn_query, Module, Runner};

use provwasm_std::types::provenance::hold::v1::{
    AccountHold, GetAllHoldsRequest, GetAllHoldsResponse, GetHoldsRequest, GetHoldsResponse,
};

pub struct Hold<'a, R: Runner<'a>> {
    runner: &'a R,
}

impl<'a, R: Runner<'a>> Module<'a, R> for Hold<'a, R> {
    fn new(runner: &'a R) -> Self {
        Self { runner }
    }
}

impl<'a, R> Hold<'a, R>
where
    R: Runner<'a>,
{
    fn_execute! {
        pub account_hold: AccountHold["/provenance.hold.v1.AccountHold"] => ()
    }

    fn_query! {
        pub query_get_holds ["/provenance.hold.v1.Query/GetHolds"]: GetHoldsRequest => GetHoldsResponse
    }

    fn_query! {
        pub query_get_all_holds ["/provenance.hold.v1.Query/GetAllHolds"]: GetAllHoldsRequest => GetAllHoldsResponse
    }
}

如果生成的宏函数不够好,您可以手动编写自己的函数。请参阅模块目录以获取更多灵感。

依赖项

~31–47MB
~1M SLoC