provwasm-test-tube

Crates.ioprovwasm-test-tube
lib.rsprovwasm-test-tube
version0.2.0
sourcesrc
created_at2024-04-29 22:03:45.418451
updated_at2024-09-30 09:07:33.46949
descriptionlibrary for building smart contract integration testing environments for Provenance Blockchain in Rust
homepage
repositoryhttps://github.com/provenance-io/provwasm-test-tube
max_upload_size
id1224526
size280,919
kwt (kwtalley)

documentation

README

provwasm-test-tube

provwasm-test-tube on crates.io Docs

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

Table of Contents

Compatibility

provwasm-test-tube provwasm provenance
0.2.0 2.4.0 1.19.1
0.1.0 2.2.0 1.18.0

Getting Started

To demonstrate how provwasm-test-tube works, let use simple example contract: marker.

Here is how to setup the test:

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(1_000_000_000_000, "nhash")], 2)?;

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

    Ok(())
}

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 loads the appchain's code as a library and creates 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.

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

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

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

    Ok(())
}

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

  • build the wasm file
  • store code
  • instantiate

Then we can start interacting with our contract

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(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(())
}

Now let's execute the contract and verify that the contract's state is updated properly.

use cosmwasm_std::{coin, Binary, 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(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()),
            Some("marker test"),
            &[],
            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(())
}

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 ProvwasmTestApp 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. Module wrappers provides convenient functions to interact with the appchain's module.

Let's try interact with the Marker module:

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

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

#[test]
fn custom_module() -> Result<(), RunnerError> {
    let app = ProvwasmTestApp::default();
    let accs = app.init_accounts(&[coin(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(())
}

Custom Module Wrapper

You might not find wrapper you want to use or the provided wrapper is too verbose. Good new is, it's trivial to create your own wrapper easily.

Here is how you can redefine Hold module wrapper as a library user:

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
    }
}

If the macro generated function is not good enough for you, you can write your own function manually. See module directory for more inspiration.

Commit count: 119

cargo fmt