coreum-test-tube

coreum-test-tube on crates.io Docs

CosmWasm x Coreum integration testing library that, unlike cw-multi-test, it allows you to test your cosmwasm contract against real chain's logic instead of mocks.

Table of Contents

Getting Started

To demonstrate how coreum-test-tube works, let use simple example contract: cw-whitelist from cw-plus.

Here is how to setup the test:

```rust use cosmwasmstd::Coin; use coreumtest_tube::CoreumTestApp;

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

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

let signer = app .initaccount(&[Coin::new(100000000000000000000u128, FEEDENOM)]) .unwrap();

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

Now we have the appchain instance and two accounts, let's interact with the chain. This does not run Docker instance or spawning external process, it just load the appchain's code as a library and creates an in-memory instance.

Note that init_account is a convenience function that creates an account with an initial balance. If you want to create just many accounts, you can use init_accounts instead. There are plenty of convenience functions defined which are defined in the package.

```rust use cosmwasmstd::Coin; use coreumtest_tube::CoreumTestApp;

let app = CoreumTestApp::new();

let accs = app .initaccounts( &[ Coin::new(1000000000000, FEEDENOM), Coin::new(1000000000000, FEE_DENOM), ], 2, ) .unwrap();

let account1 = &accs[0]; let account2 = &accs[1]; ```

Now if we want to test a cosmwasm contract, we need to

```rust use cosmwasmstd::Coin; use cw1whitelist::msg::{InstantiateMsg}; // for instantiating cw1whitelist contract, which is already in a public crate use coreumtest_tube::{Account, Module, CoreumTestApp, Wasm};

let app = CoreumTestApp::new(); let accs = app .initaccounts( &[ Coin::new(1000000000000, FEEDENOM), Coin::new(1000000000000, FEE_DENOM), ], 2, ) .unwrap();

let account1 = &accs[0]; let account2 = &accs[1]; ```

To test our smart contract we must first build an optimized wasm file so that we can store it. For this example, as already mentioned, we will use cw1_whitelist, which we already have compiled in the test_artifacts directory. To get more information about this contract you can check cw-plus.

``rust //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 wasmbytecode = std::fs::read("./testartifacts/cw1whitelist.wasm").unwrap(); let codeid = wasm .storecode(&wasmbytecode, None, &signer) .unwrap() .data .code_id;

// Instantiate contract with initial admin (signer) account defined beforehand and make admin list mutable let contractaddr = wasm .instantiate( codeid, &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::( &contract_addr, &ExecuteMsg::UpdateAdmins { admins: vec![user.address()], }, &vec![], &signer, ) .unwrap();

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

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

```

Debugging

In your contract code, if you want to debug, you can use deps.api.debug(..) which will prints the debug message to stdout. wasmd disabled this by default but CoreumTestApp allows stdout emission so that you can debug your smart contract while running tests.

Using Module Wrapper

In some cases, you might want interact directly with appchain logic to setup the environment or query appchain's state, instead of testing smart contracts. Module wrappers provide convenient functions to interact with the appchain's module. You can interact with all Coreum native modules using these wrappers.

Let's try interact with AssetFT module, while, at the same time, interacting with the native Bank module.

```rust use cosmwasmstd::Coin; use coreumtest_tube::{Account, Module, CoreumTestApp, Bank, AssetFT};

let app = CoreumTestApp::new();

let signer = app .initaccount(&[Coin::new(100000000000000000000u128, FEEDENOM)]) .unwrap(); let receiver = app .initaccount(&[Coin::new(100000000000000000000u128, FEEDENOM)]) .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 requestparams = assetft.queryparams(&QueryParamsRequest {}).unwrap(); asserteq!( requestparams.params.unwrap().issuefee.unwrap(), BaseCoin { amount: 10000000u128.tostring(), denom: FEEDENOM.tostring(), } );

// Issue a new native asset with the following information assetft. issue( MsgIssue { issuer: signer.address(), symbol: "TEST".tostring(), subunit: "utest".tostring(), precision: 6, initialamount: "10".tostring(), description: "testdescription".tostring(), features: vec![MINTING as i32], burnrate: "0".tostring(), sendcommissionrate: "0".to_string(), }, &signer, ) .unwrap();

// Query the new asset and verify that the initialamount is correct. let denom = format!("{}-{}", "utest", signer.address()).tolowercase(); let requestbalance = assetft .querybalance(&QueryBalanceRequest { account: signer.address(), denom: denom.clone(), }) .unwrap() asserteq!(requestbalance.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".tostring(), }), }, &signer, ) .unwrap() let requestbalance = assetft .querybalance(&QueryBalanceRequest { account: signer.address(), denom: denom.clone(), }) .unwrap() asserteq!(requestbalance.balance, "1000".tostring());

// 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 { fromaddress: signer.address(), toaddress: receiver.address(), amount: vec![BaseCoin { amount: "100".tostring(), denom: denom.clone(), }], }, &signer, ) .unwrap() let requestbalance = assetft .querybalance(&QueryBalanceRequest { account: signer.address(), denom: denom.clone(), }) .unwrap() asserteq!(requestbalance.balance, "900".tostring()) let requestbalance = assetft .querybalance(&QueryBalanceRequest { account: receiver.address(), denom: denom.clone(), }) .unwrap() asserteq!(requestbalance.balance, "100".to_string()); ```

Versioning

The version of coreum-test-tube is determined by the versions of its dependencies, Coreum and test-tube, as well as its own changes. The version is represented in the format A.B.C, where:

When a new version of Coreum is released and contains breaking changes, we will also release breaking changes from test-tube if any and increment the major version of coreum-test-tube. This way, it's clear that the new version of coreum-test-tube is not backwards-compatible with previous versions.

When adding a new feature to coreum-test-tube that is backward-compatible, the minor version number will be incremented.

When fixing bugs or making other changes that are coreum-test-tube specific and backward-compatible, the patch number will be incremented.

Please review the upgrade guide for upgrading the package, in case of breaking changes.

It is important to note that we track the version of the package independent of the version of dependencies.