#cosmwasm #integration #tube #integration-tests #testing #persistence #module

persistence-test-tube

Persistence 的 CosmWasm 集成测试

11 个版本 (4 个稳定版)

1.1.2 2023 年 12 月 1 日
1.0.0 2023 年 11 月 9 日
0.2.2-test2023 年 10 月 10 日
0.1.2 2023 年 10 月 5 日

#6#tube

Download history 3/week @ 2024-03-13 5/week @ 2024-03-27 6/week @ 2024-04-03

102 每月下载量

MIT/Apache

4MB
6.5K SLoC

Go 5K SLoC // 0.1% comments Shell 1K SLoC // 0.0% comments Rust 468 SLoC // 0.5% comments

osmosis-test-tube

osmosis-test-tube on crates.io Docs

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

目录

入门

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

以下是设置测试的方法

use cosmwasm_std::Coin;
use osmosis_test_tube::OsmosisTestApp;

// create new osmosis appchain instance.
let app = OsmosisTestApp::new();

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

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

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

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

use cosmwasm_std::Coin;
use osmosis_test_tube::OsmosisTestApp;

let app = OsmosisTestApp::new();

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

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

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

然后我们可以开始与我们的合约交互。让我们这样做。

use cosmwasm_std::Coin;
use cw1_whitelist::msg::{InstantiateMsg}; // for instantiating cw1_whitelist contract
use osmosis_test_tube::{Account, Module, OsmosisTestApp, Wasm};

let app = OsmosisTestApp::new();
let accs = app
    .init_accounts(
        &[
            Coin::new(1_000_000_000_000, "uatom"),
            Coin::new(1_000_000_000_000, "uosmo"),
        ],
        2,
    )
    .unwrap();
let admin = &accs[0];
let new_admin = &accs[1];

// ============= NEW CODE ================

// `Wasm` is the module we use to interact with cosmwasm releated logic on the appchain
// it implements `Module` trait which you will see more later.
let wasm = Wasm::new(&app);

// Load compiled wasm bytecode
let wasm_byte_code = std::fs::read("./test_artifacts/cw1_whitelist.wasm").unwrap();
let code_id = wasm
    .store_code(&wasm_byte_code, None, admin)
    .unwrap()
    .data
    .code_id;

注意,在这个示例中,它从 cw-plus 发布版 加载 wasm 字节码,只是为了简单演示。您可能需要运行 cargo wasm 并在 target/wasm32-unknown-unknown/release/<contract_name>.wasm 中找到您的 wasm 文件。

use cosmwasm_std::Coin;
use cw1_whitelist::msg::{InstantiateMsg, QueryMsg, AdminListResponse};
use osmosis_test_tube::{Account, Module, OsmosisTestApp, Wasm};

let app = OsmosisTestApp::new();
let accs = app
    .init_accounts(
        &[
            Coin::new(1_000_000_000_000, "uatom"),
            Coin::new(1_000_000_000_000, "uosmo"),
        ],
        2,
    )
    .unwrap();
let admin = &accs[0];
let new_admin = &accs[1];

let wasm = Wasm::new(&app);


let wasm_byte_code = std::fs::read("./test_artifacts/cw1_whitelist.wasm").unwrap();
let code_id = wasm
    .store_code(&wasm_byte_code, None, admin)
    .unwrap()
    .data
    .code_id;

// ============= NEW CODE ================

// instantiate contract with initial admin and make admin list mutable
let init_admins = vec![admin.address()];
let contract_addr = wasm
    .instantiate(
        code_id,
        &InstantiateMsg {
            admins: init_admins.clone(),
            mutable: true,
        },
        None, // contract admin used for migration, not the same as cw1_whitelist admin
        None, // contract label
        &[], // funds
        admin, // signer
    )
    .unwrap()
    .data
    .address;

// query contract state to check if contract instantiation works properly
let admin_list = wasm
    .query::<QueryMsg, AdminListResponse>(&contract_addr, &QueryMsg::AdminList {})
    .unwrap();

assert_eq!(admin_list.admins, init_admins);
assert!(admin_list.mutable);

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

use cosmwasm_std::Coin;
use cw1_whitelist::msg::{InstantiateMsg, QueryMsg, ExecuteMsg, AdminListResponse};
use osmosis_test_tube::{Account, Module, OsmosisTestApp, Wasm};

let app = OsmosisTestApp::new();
let accs = app
    .init_accounts(
        &[
            Coin::new(1_000_000_000_000, "uatom"),
            Coin::new(1_000_000_000_000, "uosmo"),
        ],
        2,
    )
    .unwrap();
let admin = &accs[0];
let new_admin = &accs[1];

let wasm = Wasm::new(&app);


let wasm_byte_code = std::fs::read("./test_artifacts/cw1_whitelist.wasm").unwrap();
let code_id = wasm
    .store_code(&wasm_byte_code, None, admin)
    .unwrap()
    .data
    .code_id;

// instantiate contract with initial admin and make admin list mutable
let init_admins = vec![admin.address()];
let contract_addr = wasm
    .instantiate(
        code_id,
        &InstantiateMsg {
            admins: init_admins.clone(),
            mutable: true,
        },
        None, // contract admin used for migration, not the same as cw1_whitelist admin
        None, // contract label
        &[], // funds
        admin, // signer
    )
    .unwrap()
    .data
    .address;

let admin_list = wasm
    .query::<QueryMsg, AdminListResponse>(&contract_addr, &QueryMsg::AdminList {})
    .unwrap();

assert_eq!(admin_list.admins, init_admins);
assert!(admin_list.mutable);

// ============= NEW CODE ================

// update admin list and rechec the state
let new_admins = vec![new_admin.address()];
wasm.execute::<ExecuteMsg>(
    &contract_addr,
    &ExecuteMsg::UpdateAdmins {
        admins: new_admins.clone(),
    },
    &[],
    admin,
)
.unwrap();

let admin_list = wasm
    .query::<QueryMsg, AdminListResponse>(&contract_addr, &QueryMsg::AdminList {})
    .unwrap();

assert_eq!(admin_list.admins, new_admins);
assert!(admin_list.mutable);

调试

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

使用模块包装器

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

让我们尝试与Gamm模块交互

use cosmwasm_std::Coin;
use osmosis_test_tube::{Account, Module, OsmosisTestApp, Gamm};

let app = OsmosisTestApp::default();
let alice = app
    .init_account(&[
        Coin::new(1_000_000_000_000, "uatom"),
        Coin::new(1_000_000_000_000, "uosmo"),
    ])
    .unwrap();

// create Gamm Module Wrapper
let gamm = Gamm::new(&app);

// create balancer pool with basic configuration
let pool_liquidity = vec![Coin::new(1_000, "uatom"), Coin::new(1_000, "uosmo")];
let pool_id = gamm
    .create_basic_pool(&pool_liquidity, &alice)
    .unwrap()
    .data
    .pool_id;

// query pool and assert if the pool is created successfully
let pool = gamm.query_pool(pool_id).unwrap();
assert_eq!(
    pool_liquidity
        .into_iter()
        .map(|c| c.into())
        .collect::<Vec<osmosis_std::types::cosmos::base::v1beta1::Coin>>(),
    pool.pool_assets
        .into_iter()
        .map(|a| a.token.unwrap())
        .collect::<Vec<osmosis_std::types::cosmos::base::v1beta1::Coin>>(),
);

自定义模块包装器

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

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

use osmosis_std::types::osmosis::gamm::{
    poolmodels::balancer::v1beta1::{MsgCreateBalancerPool, MsgCreateBalancerPoolResponse},
};
use osmosis_std::types::osmosis::gamm::v2::{QuerySpotPriceRequest, QuerySpotPriceResponse};

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


// Boilerplate code, copy and rename should just do the trick
pub struct Gamm<'a, R: Runner<'a>> {
    runner: &'a R,
}

impl<'a, R: Runner<'a>> Module<'a, R> for Gamm<'a, R> {
    fn new(runner: &'a R) -> Self {
        Self { runner }
    }
}
// End Boilerplate code

impl<'a, R> Gamm<'a, R>
where
    R: Runner<'a>,
{
    // macro for creating execute function
    fn_execute! {
        // (pub)? <fn_name>: <request_type> => <response_type>
        pub create_balancer_pool: MsgCreateBalancerPool => MsgCreateBalancerPoolResponse
    }

    // macro for creating query function
    fn_query! {
        // (pub)? <fn_name> [<method_path>]: <request_type> => <response_type>
        pub query_spot_price ["/osmosis.gamm.v2.Query/SpotPrice"]: QuerySpotPriceRequest => QuerySpotPriceResponse
    }
}

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

版本控制

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

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

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

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

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

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

请注意,我们跟踪包的版本,而不考虑依赖项的版本。

依赖项

~21–36MB
~632K SLoC