cosmwasm_vm/testing/
instance.rs

1//! This file has some helpers for integration tests.
2//! They should be imported via full path to ensure there is no confusion
3//! use cosmwasm_vm::testing::X
4use cosmwasm_std::Coin;
5use std::collections::HashSet;
6
7use crate::capabilities::capabilities_from_csv;
8use crate::compatibility::check_wasm;
9use crate::instance::{Instance, InstanceOptions};
10use crate::internals::Logger;
11use crate::size::Size;
12use crate::{Backend, BackendApi, Querier, Storage, WasmLimits};
13
14use super::mock::{MockApi, MOCK_CONTRACT_ADDR};
15use super::querier::MockQuerier;
16use super::storage::MockStorage;
17
18/// This gas limit is used in integration tests and should be high enough to allow a reasonable
19/// number of contract executions and queries on one instance. For this reason it is significantly
20/// higher than the limit for a single execution that we have in the production setup.
21const DEFAULT_GAS_LIMIT: u64 = 2_000_000_000; // ~2.0ms
22const DEFAULT_MEMORY_LIMIT: Option<Size> = Some(Size::mebi(16));
23
24pub fn mock_instance(
25    wasm: &[u8],
26    contract_balance: &[Coin],
27) -> Instance<MockApi, MockStorage, MockQuerier> {
28    mock_instance_with_options(
29        wasm,
30        MockInstanceOptions {
31            contract_balance: Some(contract_balance),
32            ..Default::default()
33        },
34    )
35}
36
37pub fn mock_instance_with_failing_api(
38    wasm: &[u8],
39    contract_balance: &[Coin],
40    backend_error: &'static str,
41) -> Instance<MockApi, MockStorage, MockQuerier> {
42    mock_instance_with_options(
43        wasm,
44        MockInstanceOptions {
45            contract_balance: Some(contract_balance),
46            backend_error: Some(backend_error),
47            ..Default::default()
48        },
49    )
50}
51
52pub fn mock_instance_with_balances(
53    wasm: &[u8],
54    balances: &[(&str, &[Coin])],
55) -> Instance<MockApi, MockStorage, MockQuerier> {
56    mock_instance_with_options(
57        wasm,
58        MockInstanceOptions {
59            balances,
60            ..Default::default()
61        },
62    )
63}
64
65/// Creates an instance from the given Wasm bytecode.
66/// The gas limit is measured in [CosmWasm gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
67pub fn mock_instance_with_gas_limit(
68    wasm: &[u8],
69    gas_limit: u64,
70) -> Instance<MockApi, MockStorage, MockQuerier> {
71    mock_instance_with_options(
72        wasm,
73        MockInstanceOptions {
74            gas_limit,
75            ..Default::default()
76        },
77    )
78}
79
80#[derive(Debug)]
81pub struct MockInstanceOptions<'a> {
82    // dependencies
83    pub balances: &'a [(&'a str, &'a [Coin])],
84    /// This option is merged into balances and might override an existing value
85    pub contract_balance: Option<&'a [Coin]>,
86    /// When set, all calls to the API fail with BackendError::Unknown containing this message
87    pub backend_error: Option<&'static str>,
88
89    // instance
90    pub available_capabilities: HashSet<String>,
91    /// Gas limit measured in [CosmWasm gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
92    pub gas_limit: u64,
93    /// Memory limit in bytes. Use a value that is divisible by the Wasm page size 65536, e.g. full MiBs.
94    pub memory_limit: Option<Size>,
95}
96
97impl MockInstanceOptions<'_> {
98    fn default_capabilities() -> HashSet<String> {
99        #[allow(unused_mut)]
100        let mut out = capabilities_from_csv(
101            "ibc2,iterator,staking,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,cosmwasm_2_0,cosmwasm_2_1,cosmwasm_2_2,cosmwasm_3_0",
102        );
103        #[cfg(feature = "stargate")]
104        out.insert("stargate".to_string());
105        out
106    }
107}
108
109impl Default for MockInstanceOptions<'_> {
110    fn default() -> Self {
111        Self {
112            // dependencies
113            balances: Default::default(),
114            contract_balance: Default::default(),
115            backend_error: None,
116
117            // instance
118            available_capabilities: Self::default_capabilities(),
119            gas_limit: DEFAULT_GAS_LIMIT,
120            memory_limit: DEFAULT_MEMORY_LIMIT,
121        }
122    }
123}
124
125pub fn mock_instance_with_options(
126    wasm: &[u8],
127    options: MockInstanceOptions,
128) -> Instance<MockApi, MockStorage, MockQuerier> {
129    check_wasm(
130        wasm,
131        &options.available_capabilities,
132        &WasmLimits::default(),
133        Logger::Off,
134    )
135    .unwrap();
136    let contract_address = MOCK_CONTRACT_ADDR;
137
138    // merge balances
139    let mut balances = options.balances.to_vec();
140    if let Some(contract_balance) = options.contract_balance {
141        // Remove old entry if exists
142        if let Some(pos) = balances.iter().position(|item| item.0 == contract_address) {
143            balances.remove(pos);
144        }
145        balances.push((contract_address, contract_balance));
146    }
147
148    let api = if let Some(backend_error) = options.backend_error {
149        MockApi::new_failing(backend_error)
150    } else {
151        MockApi::default()
152    };
153
154    let backend = Backend {
155        api,
156        storage: MockStorage::default(),
157        querier: MockQuerier::new(&balances),
158    };
159    let memory_limit = options.memory_limit;
160    let options = InstanceOptions {
161        gas_limit: options.gas_limit,
162    };
163    Instance::from_code(wasm, backend, options, memory_limit).unwrap()
164}
165
166/// Creates InstanceOptions for testing
167pub fn mock_instance_options() -> (InstanceOptions, Option<Size>) {
168    (
169        InstanceOptions {
170            gas_limit: DEFAULT_GAS_LIMIT,
171        },
172        DEFAULT_MEMORY_LIMIT,
173    )
174}
175
176/// Runs a series of IO tests, hammering especially on allocate and deallocate.
177/// This could be especially useful when run with some kind of leak detector.
178pub fn test_io<A, S, Q>(instance: &mut Instance<A, S, Q>)
179where
180    A: BackendApi + 'static,
181    S: Storage + 'static,
182    Q: Querier + 'static,
183{
184    let sizes: Vec<usize> = vec![0, 1, 3, 10, 200, 2000, 5 * 1024];
185    let bytes: Vec<u8> = vec![0x00, 0xA5, 0xFF];
186
187    for size in sizes.into_iter() {
188        for byte in bytes.iter() {
189            let original = vec![*byte; size];
190            let wasm_ptr = instance
191                .allocate(original.len())
192                .expect("Could not allocate memory");
193            instance
194                .write_memory(wasm_ptr, &original)
195                .expect("Could not write data");
196            let wasm_data = instance.read_memory(wasm_ptr, size).expect("error reading");
197            assert_eq!(
198                original, wasm_data,
199                "failed for size {size}; expected: {original:?}; actual: {wasm_data:?}"
200            );
201            instance
202                .deallocate(wasm_ptr)
203                .expect("Could not deallocate memory");
204        }
205    }
206}