CosmWasm x Injective integration testing library that, unlike cw-multi-test
, it allows you to test your cosmwasm contract against real chain's logic instead of mocks.
As the project depends on currently private repos, no crate is published rather please refer to CHANGELOG
for features and update information.
To demonstrate how injective-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 injectivetest_tube::InjectiveTestApp;
// create new injective appchain instance. let app = InjectiveTestApp::new();
// create new account with initial funds let accs = app .initaccounts( &[ Coin::new(1000000000000, "usdt"), Coin::new(1000000000_000, "inj"), ], 2, ) .unwrap();
let admin = &accs[0]; let new_admin = &accs[1]; ```
Now we have the appchain instance and accounts that have some initial balances and can interact with the appchain. This does not run Docker instance or spawning external process, it just load the appchain's code as a library create an in memory instance.
Note that init_accounts
is a convenience function that creates multiple accounts with the same initial balance.
If you want to create just one account, you can use init_account
instead.
```rust use cosmwasmstd::Coin; use injectivetest_tube::InjectiveTestApp;
let app = InjectiveTestApp::new();
let account = app.initaccount(&[ Coin::new(1000000000000, "usdt"), Coin::new(1000000000_000, "inj"), ]); ```
Now if we want to test a cosmwasm contract, we need to
Then we can start interacting with our contract. Let's do just that.
```rust use cosmwasmstd::Coin; use cw1whitelist::msg::{InstantiateMsg}; // for instantiating cw1whitelist contract use injectivetest_tube::{Account, Module, InjectiveTestApp, Wasm};
let app = InjectiveTestApp::new(); let accs = app .initaccounts( &[ Coin::new(1000000000000, "usdt"), Coin::new(1000000000000, "inj"), ], 2, ) .unwrap(); let admin = &accs[0]; let newadmin = &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 wasmbytecode = std::fs::read("./testartifacts/cw1whitelist.wasm").unwrap(); let codeid = wasm .storecode(&wasmbytecode, None, admin) .unwrap() .data .code_id; ```
Not that in this example, it loads wasm bytecode from cw-plus release for simple demonstration purposes.
You might want to run cargo wasm
and find your wasm file in target/wasm32-unknown-unknown/release/<contract_name>.wasm
.
```rust use cosmwasmstd::Coin; use cw1whitelist::msg::{InstantiateMsg, QueryMsg, AdminListResponse}; use injectivetesttube::{Account, Module, InjectiveTestApp, Wasm};
let app = InjectiveTestApp::new(); let accs = app .initaccounts( &[ Coin::new(1000000000000, "usdt"), Coin::new(1000000000000, "inj"), ], 2, ) .unwrap(); let admin = &accs[0]; let newadmin = &accs[1];
let wasm = Wasm::new(&app);
let wasmbytecode = std::fs::read("./testartifacts/cw1whitelist.wasm").unwrap(); let codeid = wasm .storecode(&wasmbytecode, None, admin) .unwrap() .data .code_id;
// ============= NEW CODE ================
// instantiate contract with initial admin and make admin list mutable let initadmins = vec![admin.address()]; let contractaddr = wasm .instantiate( codeid, &InstantiateMsg { admins: initadmins.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 adminlist = wasm
.query::
asserteq!(adminlist.admins, initadmins); assert!(adminlist.mutable); ```
Now let's execute the contract and verify that the contract's state is updated properly.
```rust use cosmwasmstd::Coin; use cw1whitelist::msg::{InstantiateMsg, QueryMsg, ExecuteMsg, AdminListResponse}; use injectivetesttube::{Account, Module, InjectiveTestApp, Wasm};
let app = InjectiveTestApp::new(); let accs = app .initaccounts( &[ Coin::new(1000000000000, "usdt"), Coin::new(1000000000000, "inj"), ], 2, ) .unwrap(); let admin = &accs[0]; let newadmin = &accs[1];
let wasm = Wasm::new(&app);
let wasmbytecode = std::fs::read("./testartifacts/cw1whitelist.wasm").unwrap(); let codeid = wasm .storecode(&wasmbytecode, None, admin) .unwrap() .data .code_id;
// instantiate contract with initial admin and make admin list mutable let initadmins = vec![admin.address()]; let contractaddr = wasm .instantiate( codeid, &InstantiateMsg { admins: initadmins.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 adminlist = wasm
.query::
asserteq!(adminlist.admins, initadmins); assert!(adminlist.mutable);
// ============= NEW CODE ================
// update admin list and rechec the state
let newadmins = vec![newadmin.address()];
wasm.execute::
let adminlist = wasm
.query::
asserteq!(adminlist.admins, newadmins); assert!(adminlist.mutable); ```
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 InjectiveTestApp
allows stdout emission so that you can debug your smart contract while running tests.
In some cases, you might want interact directly with appchain logic to setup the environment or query appchain's state. Module wrappers provides convenient functions to interact with the appchain's module.
Let's try interact with Exchange
module:
```rust use cosmwasmstd::{Addr, Coin}; use injectivestd::types::injective::exchange::v1beta1::{ MarketStatus, MsgInstantSpotMarketLaunch, QuerySpotMarketsRequest, QuerySpotMarketsResponse, SpotMarket, }; use injectivetesttube::{Account, Exchange, InjectiveTestApp}; use testtubeinj::Module;
let app = InjectiveTestApp::new(); let signer = app .initaccount(&[ Coin::new(10000000000000000000000u128, "inj"), Coin::new(100000000000000000000u128, "usdt"), ]) .unwrap(); let trader = app .initaccount(&[ Coin::new(10000000000000000000000u128, "inj"), Coin::new(100000000000000000000u128, "usdt"), ]) .unwrap(); let exchange = Exchange::new(&app);
exchange .instantspotmarketlaunch( MsgInstantSpotMarketLaunch { sender: signer.address(), ticker: "INJ/USDT".toowned(), basedenom: "inj".toowned(), quotedenom: "usdt".toowned(), minpriceticksize: "10000".toowned(), minquantityticksize: "100000".toowned(), }, &signer, ) .unwrap();
exchange .instantspotmarketlaunch( MsgInstantSpotMarketLaunch { sender: signer.address(), ticker: "INJ/USDT".toowned(), basedenom: "inj".toowned(), quotedenom: "usdt".toowned(), minpriceticksize: "10000".toowned(), minquantityticksize: "100000".toowned(), }, &signer, ) .unwrap_err();
app.increase_time(1u64);
let spotmarkets = exchange .queryspotmarkets(&QuerySpotMarketsRequest { status: "Active".toowned(), market_ids: vec![], }) .unwrap();
let expectedresponse = QuerySpotMarketsResponse { markets: vec![SpotMarket { ticker: "INJ/USDT".tostring(), basedenom: "inj".tostring(), quotedenom: "usdt".tostring(), makerfeerate: "-100000000000000".tostring(), takerfeerate: "1000000000000000".tostring(), relayerfeesharerate: "400000000000000000".tostring(), marketid: "0xd5a22be807011d5e42d5b77da3f417e22676efae494109cd01c242ad46630115" .tostring(), status: MarketStatus::Active.into(), minpriceticksize: "10000".tostring(), minquantityticksize: "100000".tostring(), }], }; asserteq!(spotmarkets, expected_response); ```
Additional examples can be found in the modules directory.
The version of injective-test-tube is determined by the versions of its dependencies, injective 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 injective is released and contains breaking changes, we will also release breaking changes from test-tube if any and increment the major version of injective-test-tube. This way, it's clear that the new version of injective-test-tube is not backwards-compatible with previous versions.
When adding a new feature to injective-test-tube that is backward-compatible, the minor version number will be incremented.
When fixing bugs or making other changes that are injective-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.