3 个稳定版本

4.0.0 2024年7月3日
3.0.1 2023年11月7日
3.0.0 2023年9月21日

#4#tube

MIT/Apache

170KB
3K SLoC

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

coreum-test-tube

coreum-test-tube on crates.io Docs

一个 CosmWasm x Coreum 集成测试库,与 cw-multi-test 不同,它允许您测试您的 cosmwasm 合约与真实链的逻辑,而不是模拟。

目录

入门指南

为了演示 coreum-test-tube 的工作原理,让我们用一个简单的示例合约:来自 cw-pluscw-whitelist

这是设置测试的方法

use cosmwasm_std::Coin;
use coreum_test_tube::CoreumTestApp;

// Create new Coreum appchain instance.
let app = CoreumTestApp::new();

// Create a new account with initial funds and one without initial funds
use coreum_test_tube::runner:app::FEE_DENOM;

let signer = app
            .init_account(&[Coin::new(100_000_000_000_000_000_000u128, FEE_DENOM)])
            .unwrap();

let user   = app.init_account(&[]).unwrap();

现在我们有 appchain 实例和两个账户,让我们与链交互。这不会运行 Docker 实例或启动外部进程,它只是将 appchain 的代码作为库加载并创建一个内存实例。

请注意,init_account 是一个便利函数,用于创建一个带有初始余额的账户。如果您想创建很多账户,可以使用 init_accounts。该包中定义了大量的便利函数。

use cosmwasm_std::coins;
use coreum_test_tube::CoreumTestApp;

let app = CoreumTestApp::new();

let accounts = app
            .init_accounts(&coins(100_000_000_000, FEE_DENOM), 4)
            .unwrap();

let acc1 = accounts.get(0).unwrap();
let acc2 = accounts.get(1).unwrap();
let acc3 = accounts.get(2).unwrap();
let acc4 = accounts.get(3).unwrap();

现在,如果我们想测试一个 cosmwasm 合约,我们需要

  • 一个构建和优化的 wasm 文件(我们将使用放置在 test_artifacts 目录中的 cw1_whitelist.wasm 合约)
  • 存储代码
  • 实例化
  • 执行或查询
use cosmwasm_std::coins;
use cw1_whitelist::msg::{InstantiateMsg}; // for instantiating cw1_whitelist contract, which is already in a public crate
use coreum_test_tube::{Account, Module, CoreumTestApp, Wasm};

let app = CoreumTestApp::new();
let accs = app
        .init_accounts(&coins(100_000_000_000, FEE_DENOM), 2)
        .unwrap();

let account1 = &accs.get(0).unwrap();
let account2 = &accs.get(1).unwrap();;

为了测试我们的智能合约,我们首先需要构建一个优化的 wasm 文件,以便我们可以存储它。对于这个例子,正如已经提到的,我们将使用 cw1_whitelist,它已经在 test_artifacts 目录中编译。有关此合约的更多信息,您可以检查 cw-plus

// `Wasm` is the module we use to interact with cosmwasm releated logic on the appchain
let wasm = Wasm::new(&app);

// Store compiled wasm code on the appchain and retrieve its code id
let wasm_byte_code = std::fs::read("./test_artifacts/cw1_whitelist.wasm").unwrap();
let code_id = wasm
            .store_code(&wasm_byte_code, None, &signer)
            .unwrap()
            .data
            .code_id;

// Instantiate contract with initial admin (signer) account defined beforehand and make admin list mutable
let contract_addr = wasm
            .instantiate(
                code_id,
                &InstantiateMsg {
                    admins: vec![signer.address()],
                    mutable: true,
                },
                None,
                "label".into(),
                &[],
                &signer,
            )
            .unwrap()
            .data
            .address;

// Execute the contract to modify admin to user address

wasm.execute::<ExecuteMsg>(
        &contract_addr,
        &ExecuteMsg::UpdateAdmins {
            admins: vec![user.address()],
        },
        &vec![],
        &signer,
    )
    .unwrap();

// Query the contract to verify that the admin has been updated correctly.
let admin_list = wasm
        .query::<QueryMsg, AdminListResponse>(&contract_addr, &QueryMsg::AdminList {})
        .unwrap();

assert_eq!(admin_list.admins, vec![user.address()]);
assert!(admin_list.mutable);

调试

在您的合约代码中,如果您想进行调试,可以使用deps.api.debug(..),这将把调试信息打印到标准输出。默认情况下,wasmd已禁用此功能,但CoreumTestApp允许标准输出输出,以便您可以在运行测试的同时调试智能合约。

使用模块包装器

在某些情况下,您可能想直接与appchain逻辑交互来设置环境或查询appchain的状态,而不是测试智能合约。模块包装器提供了方便的函数来与appchain的模块交互。您可以使用这些包装器与所有Coreum本地模块进行交互。

让我们尝试与AssetFT模块交互,同时与本地Bank模块交互。

use cosmwasm_std::Coin;
use coreum_test_tube::{Account, Module, CoreumTestApp, Bank, AssetFT};

let app = CoreumTestApp::new();

let signer = app
    .init_account(&[Coin::new(100_000_000_000_000_000_000u128, FEE_DENOM)])
     .unwrap();
let receiver = app
    .init_account(&[Coin::new(100_000_000_000_000_000_000u128, FEE_DENOM)])
    .unwrap();

// Create AssetFT Module Wrapper
let assetft = AssetFT::new(&app);
// Create Bank Module Wrapper
let bank = Bank::new(&app);

// Query the issue fee and assert if the fee is correct
let request_params = assetft.query_params(&QueryParamsRequest {}).unwrap();
assert_eq!(
    request_params.params.unwrap().issue_fee.unwrap(),
    BaseCoin {
        amount: 10000000u128.to_string(),
        denom: FEE_DENOM.to_string(),
    }
);

// Issue a new native asset with the following information
assetft.
    issue(
        MsgIssue {
            issuer: signer.address(),
            symbol: "TEST".to_string(),
            subunit: "utest".to_string(),
            precision: 6,
            initial_amount: "10".to_string(),
            description: "test_description".to_string(),
            features: vec![MINTING as i32],
            burn_rate: "0".to_string(),
            send_commission_rate: "0".to_string(),
            uri: "test_uri".to_string(),
            uri_hash: "test_uri_hash".to_string(),
        },
        &signer,
    )
    .unwrap();

// Query the new asset and verify that the initial_amount is correct.
let denom = format!("{}-{}", "utest", signer.address()).to_lowercase();
let request_balance = assetft
    .query_balance(&QueryBalanceRequest {
        account: signer.address(),
        denom: denom.clone(),
    })
    .unwrap()
assert_eq!(request_balance.balance, "10".to_string());

// Mint additional tokens and verify that the balance has been updated correctly (10 + 990 = 1000)
assetft
    .mint(
        MsgMint {
            sender: signer.address(),
            coin: Some(BaseCoin {
                denom: denom.clone(),
                amount: "990".to_string(),
            }),
            recipient: signer.address(),
        },
        &signer,
    )
    .unwrap()
let request_balance = assetft
    .query_balance(&QueryBalanceRequest {
        account: signer.address(),
        denom: denom.clone(),
    })
    .unwrap()
assert_eq!(request_balance.balance, "1000".to_string());

// Using the bank module, send a transaction to another address and verify that both balances of the AssetFTs have been updated correctly.
bank.send(
    MsgSend {
        from_address: signer.address(),
        to_address: receiver.address(),
        amount: vec![BaseCoin {
            amount: "100".to_string(),
            denom: denom.clone(),
        }],
    },
    &signer,
)
.unwrap()
let request_balance = assetft
    .query_balance(&QueryBalanceRequest {
        account: signer.address(),
        denom: denom.clone(),
    })
    .unwrap()
assert_eq!(request_balance.balance, "900".to_string())
let request_balance = assetft
    .query_balance(&QueryBalanceRequest {
        account: receiver.address(),
        denom: denom.clone(),
    })
    .unwrap()
assert_eq!(request_balance.balance, "100".to_string());

版本控制

coreum-test-tube的版本由其依赖项、Coreum和test-tube的版本以及其自身更改决定。版本表示为A.B.C格式,其中

  • A是Coreum的主版本,
  • B是coreum-test-tube的次版本,
  • C是coreum-test-tube自身的补丁号。

当Coreum发布新版本并包含破坏性更改时,如果test-tube有任何破坏性更改,我们也将发布破坏性更改,并增加coreum-test-tube的主版本。这样,可以清楚地知道coreum-test-tube的新版本与旧版本不兼容。

当向coreum-test-tube添加向后兼容的新功能时,将增加次要版本号。

当修复错误或进行其他对coreum-test-tube特定且向后兼容的更改时,将增加补丁号。

请在升级包时查看升级指南,以防出现破坏性更改。

请注意,我们独立于依赖项的版本跟踪包的版本。

依赖项

~24–39MB
~700K SLoC