diff --git a/CLA.md b/CLA.md deleted file mode 100644 index 17a701ee..00000000 --- a/CLA.md +++ /dev/null @@ -1,23 +0,0 @@ -## Contributor License Agreement -The following terms are used throughout this agreement: - -* You - the person or legal entity including its affiliates asked to accept this agreement. An affiliate is any entity that controls or is controlled by the legal entity, or is under common control with it. -* Project - is an umbrella term that refers to any and all Confio OÜ open source projects. -* Contribution - any type of work that is submitted to a Project, including any modifications or additions to existing work. -* Submitted - conveyed to a Project via a pull request, commit, issue, or any form of electronic, written, or verbal communication with Confio OÜ, contributors or maintainers. - -## 1. Grant of Copyright License. - -Subject to the terms and conditions of this agreement, You grant to the Projects’ maintainers, contributors, users and to Confio OÜ a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your contributions and such derivative works. Except for this license, You reserve all rights, title, and interest in your contributions. - -## 2. Grant of Patent License. - -Subject to the terms and conditions of this agreement, You grant to the Projects’ maintainers, contributors, users and to Confio OÜ a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer your contributions, where such license applies only to those patent claims licensable by you that are necessarily infringed by your contribution or by combination of your contribution with the project to which this contribution was submitted. - -If any entity institutes patent litigation - including cross-claim or counterclaim in a lawsuit - against You alleging that your contribution or any project it was submitted to constitutes or is responsible for direct or contributory patent infringement, then any patent licenses granted to that entity under this agreement shall terminate as of the date such litigation is filed. - -## 3. Source of Contribution. - -Your contribution is either your original creation, based upon previous work that, to the best of your knowledge, is covered under an appropriate open source license and you have the right under that license to submit that work with modifications, whether created in whole or in part by you, or you have clearly identified the source of the contribution and any license or other restriction (like related patents, trademarks, and license agreements) of which you are personally aware. - -_Based in [GitHub's CLA](https://cla.github.com/agreement)__ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 14931905..dd639dde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,9 +295,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "zeroize", @@ -526,9 +526,9 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "miniz_oxide" @@ -599,7 +599,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.58", ] [[package]] @@ -712,7 +712,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.58", ] [[package]] @@ -806,9 +806,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.55" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -832,7 +832,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.55", + "syn 2.0.58", ] [[package]] diff --git a/src/app.rs b/src/app.rs index 6608ffd0..b4e644f2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -17,7 +17,7 @@ use cosmwasm_std::testing::{MockApi, MockStorage}; use cosmwasm_std::{ from_json, to_json_binary, Addr, Api, Binary, BlockInfo, ContractResult, CosmosMsg, CustomMsg, CustomQuery, Empty, Querier, QuerierResult, QuerierWrapper, QueryRequest, Record, Storage, - SystemError, SystemResult, + SystemError, SystemResult, TransactionInfo, }; use serde::{de::DeserializeOwned, Serialize}; use std::fmt::Debug; @@ -410,6 +410,7 @@ where .staking .process_queue(&self.api, &mut self.storage, &self.router, &self.block) .unwrap(); + self.router.wasm.set_transaction_info(None); self.block = block; } @@ -419,14 +420,20 @@ where .staking .process_queue(&self.api, &mut self.storage, &self.router, &self.block) .unwrap(); + self.router.wasm.set_transaction_info(None); action(&mut self.block); } - /// Returns a copy of the current block_info + /// Returns a copy of the current [BlockInfo]. pub fn block_info(&self) -> BlockInfo { self.block.clone() } + /// Returns a copy of the current [TransactionInfo]. + pub fn transaction_info(&self) -> Option { + self.router.wasm.transaction_info() + } + /// Simple helper so we get access to all the QuerierWrapper helpers, /// e.g. wrap().query_wasm_smart, query_all_balances, ... pub fn wrap(&self) -> QuerierWrapper { @@ -452,11 +459,15 @@ where storage, } = self; - transactional(&mut *storage, |write_cache, _| { - msgs.into_iter() - .map(|msg| router.execute(&*api, write_cache, block, sender.clone(), msg)) - .collect() - }) + transactional( + &mut *storage, + |write_cache, _| { + msgs.into_iter() + .map(|msg| router.execute(&*api, write_cache, block, sender.clone(), msg)) + .collect() + }, + || router.wasm.inc_transaction_index(), + ) } /// Call a smart contract in "sudo" mode. @@ -479,9 +490,11 @@ where storage, } = self; - transactional(&mut *storage, |write_cache, _| { - router.wasm.sudo(&*api, write_cache, router, block, msg) - }) + transactional( + &mut *storage, + |write_cache, _| router.wasm.sudo(&*api, write_cache, router, block, msg), + || router.wasm.inc_transaction_index(), + ) } /// Runs arbitrary SudoMsg. @@ -498,9 +511,11 @@ where storage, } = self; - transactional(&mut *storage, |write_cache, _| { - router.sudo(&*api, write_cache, block, msg) - }) + transactional( + &mut *storage, + |write_cache, _| router.sudo(&*api, write_cache, block, msg), + || router.wasm.inc_transaction_index(), + ) } } /// The Router plays a critical role in managing and directing diff --git a/src/app_builder.rs b/src/app_builder.rs index 56434c17..a9b95cf7 100644 --- a/src/app_builder.rs +++ b/src/app_builder.rs @@ -6,7 +6,7 @@ use crate::{ Wasm, WasmKeeper, }; use cosmwasm_std::testing::{mock_env, MockApi, MockStorage}; -use cosmwasm_std::{Api, BlockInfo, CustomMsg, CustomQuery, Empty, Storage}; +use cosmwasm_std::{Api, BlockInfo, CustomMsg, CustomQuery, Empty, Storage, TransactionInfo}; use serde::de::DeserializeOwned; use std::fmt::Debug; @@ -530,6 +530,12 @@ where self } + /// Overwrites the initial transaction. + pub fn with_transaction(self, transaction: Option) -> Self { + self.wasm.set_transaction_info(transaction); + self + } + /// Builds final `App`. At this point all components type have to be properly related to each /// other. If there are some generics related compilation errors, make sure that all components /// are properly relating to each other. diff --git a/src/tests/test_app.rs b/src/tests/test_app.rs index 685234eb..99c685b2 100644 --- a/src/tests/test_app.rs +++ b/src/tests/test_app.rs @@ -143,22 +143,26 @@ fn multi_level_bank_cache() { assert_eq!(router_rcpt, vec![]); // now, second level cache - transactional(&mut cache, |cache2, read| { - let msg = BankMsg::Send { - to_address: recipient_addr.clone().into(), - amount: coins(12, "eth"), - }; - app.router() - .execute(app.api(), cache2, &app.block_info(), owner_addr, msg.into()) - .unwrap(); + transactional( + &mut cache, + |cache2, read| { + let msg = BankMsg::Send { + to_address: recipient_addr.clone().into(), + amount: coins(12, "eth"), + }; + app.router() + .execute(app.api(), cache2, &app.block_info(), owner_addr, msg.into()) + .unwrap(); - // shows up in 2nd cache - let cached_rcpt = query_router(app.router(), app.api(), read, &recipient_addr); - assert_eq!(coins(25, "eth"), cached_rcpt); - let cached2_rcpt = query_router(app.router(), app.api(), cache2, &recipient_addr); - assert_eq!(coins(37, "eth"), cached2_rcpt); - Ok(()) - }) + // shows up in 2nd cache + let cached_rcpt = query_router(app.router(), app.api(), read, &recipient_addr); + assert_eq!(coins(25, "eth"), cached_rcpt); + let cached2_rcpt = query_router(app.router(), app.api(), cache2, &recipient_addr); + assert_eq!(coins(37, "eth"), cached2_rcpt); + Ok(()) + }, + || app.router.wasm.inc_transaction_index(), + ) .unwrap(); // apply first to router diff --git a/src/transactions.rs b/src/transactions.rs index 59ac0b0f..a4cc5c54 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -7,17 +7,19 @@ use std::iter; use std::iter::Peekable; use std::ops::{Bound, RangeBounds}; -/// The BTreeMap specific key-value pair reference type, as returned by `BTreeMap, T>::range`. +/// The [BTreeMap] specific key-value pair reference type, as returned by `BTreeMap, T>::range`. /// This is internal as it can change any time if the map implementation is swapped out. type BTreeMapPairRef<'a, T = Vec> = (&'a Vec, &'a T); -pub fn transactional(base: &mut dyn Storage, action: F) -> AnyResult +pub fn transactional(base: &mut dyn Storage, action: F, increment: G) -> AnyResult where F: FnOnce(&mut dyn Storage, &dyn Storage) -> AnyResult, + G: FnOnce(), { let mut cache = StorageTransaction::new(base); let res = action(&mut cache, base)?; cache.prepare().commit(base); + increment(); Ok(res) } diff --git a/src/wasm.rs b/src/wasm.rs index 35bf331b..59492081 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -19,6 +19,7 @@ use schemars::JsonSchema; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; +use std::cell::RefCell; use std::collections::BTreeMap; use std::fmt::Debug; @@ -167,6 +168,17 @@ pub trait Wasm { let storage = PrefixedStorage::multilevel(storage, &[NAMESPACE_WASM, &namespace]); Box::new(storage) } + + /// Increments transaction index. + fn inc_transaction_index(&self) {} + + /// Sets custom transaction info. + fn set_transaction_info(&self, _transaction: Option) {} + + /// Returns a copy of the current transaction info. + fn transaction_info(&self) -> Option { + None + } } /// A structure representing a default wasm keeper. @@ -179,6 +191,8 @@ pub struct WasmKeeper { address_generator: Box, /// Contract's code checksum generator. checksum_generator: Box, + /// Optional transaction info, filled when some transaction was already executed. + transaction: RefCell>, /// Just markers to make type elision fork when using it as `Wasm` trait _p: std::marker::PhantomData, } @@ -191,6 +205,7 @@ impl Default for WasmKeeper { code_data: BTreeMap::default(), address_generator: Box::new(SimpleAddressGenerator), checksum_generator: Box::new(SimpleChecksumGenerator), + transaction: RefCell::new(None), _p: std::marker::PhantomData, } } @@ -336,6 +351,23 @@ where let storage = self.contract_storage(storage, address); storage.range(None, None, Order::Ascending).collect() } + + fn inc_transaction_index(&self) { + let mut transaction_ref = self.transaction.borrow_mut(); + let Some(transaction) = transaction_ref.as_mut() else { + *transaction_ref = Some(TransactionInfo { index: 0 }); + return; + }; + transaction.index += 1; + } + + fn set_transaction_info(&self, transaction: Option) { + *self.transaction.borrow_mut() = transaction; + } + + fn transaction_info(&self) -> Option { + self.transaction.borrow().clone() + } } impl WasmKeeper { @@ -817,9 +849,11 @@ where } = msg; // execute in cache - let res = transactional(storage, |write_cache, _| { - router.execute(api, write_cache, block, contract.clone(), msg) - }); + let res = transactional( + storage, + |write_cache, _| router.execute(api, write_cache, block, contract.clone(), msg), + || self.inc_transaction_index(), + ); // call reply if meaningful if let Ok(mut r) = res { @@ -1173,18 +1207,22 @@ where // execute_submsg or App.execute_multi. // However, we need to get write and read access to the same storage in two different objects, // and this is the only way I know how to do so. - transactional(storage, |write_cache, read_store| { - let mut contract_storage = self.contract_storage_mut(write_cache, &address); - let querier = RouterQuerier::new(router, api, read_store, block); - let env = self.get_env(address, block); + transactional( + storage, + |write_cache, read_store| { + let mut contract_storage = self.contract_storage_mut(write_cache, &address); + let querier = RouterQuerier::new(router, api, read_store, block); + let env = self.get_env(address, block); - let deps = DepsMut { - storage: contract_storage.as_mut(), - api, - querier: QuerierWrapper::new(&querier), - }; - action(handler, deps, env) - }) + let deps = DepsMut { + storage: contract_storage.as_mut(), + api, + querier: QuerierWrapper::new(&querier), + }; + action(handler, deps, env) + }, + || {}, + ) } /// Saves contract data in a storage under specified address. @@ -1310,34 +1348,42 @@ mod test { let block = mock_env().block; let code_id = wasm_keeper.store_code(creator_addr, error::contract(false)); - transactional(&mut wasm_storage, |cache, _| { - // cannot register contract with unregistered codeId - wasm_keeper.register_contract( - &api, - cache, - code_id + 1, - user_addr.clone(), - admin_addr.clone(), - "label".to_owned(), - 1000, - None, - ) - }) + transactional( + &mut wasm_storage, + |cache, _| { + // cannot register contract with unregistered codeId + wasm_keeper.register_contract( + &api, + cache, + code_id + 1, + user_addr.clone(), + admin_addr.clone(), + "label".to_owned(), + 1000, + None, + ) + }, + || wasm_keeper.inc_transaction_index(), + ) .unwrap_err(); - let contract_addr = transactional(&mut wasm_storage, |cache, _| { - // we can register a new instance of this code - wasm_keeper.register_contract( - &api, - cache, - code_id, - user_addr.clone(), - admin_addr.clone(), - "label".to_owned(), - 1000, - None, - ) - }) + let contract_addr = transactional( + &mut wasm_storage, + |cache, _| { + // we can register a new instance of this code + wasm_keeper.register_contract( + &api, + cache, + code_id, + user_addr.clone(), + admin_addr.clone(), + "label".to_owned(), + 1000, + None, + ) + }, + || wasm_keeper.inc_transaction_index(), + ) .unwrap(); // verify contract data are as expected @@ -1356,19 +1402,23 @@ mod test { } ); - let err = transactional(&mut wasm_storage, |cache, _| { - // now, we call this contract and see the error message from the contract - let info = mock_info(user_addr.as_str(), &[]); - wasm_keeper.call_instantiate( - contract_addr.clone(), - &api, - cache, - &mock_router(), - &block, - info, - b"{}".to_vec(), - ) - }) + let err = transactional( + &mut wasm_storage, + |cache, _| { + // now, we call this contract and see the error message from the contract + let info = mock_info(user_addr.as_str(), &[]); + wasm_keeper.call_instantiate( + contract_addr.clone(), + &api, + cache, + &mock_router(), + &block, + info, + b"{}".to_vec(), + ) + }, + || wasm_keeper.inc_transaction_index(), + ) .unwrap_err(); // StdError from contract_error auto-converted to string @@ -1377,19 +1427,23 @@ mod test { err.downcast().unwrap() ); - let err = transactional(&mut wasm_storage, |cache, _| { - // and the error for calling an unregistered contract - let info = mock_info(user_addr.as_str(), &[]); - wasm_keeper.call_instantiate( - unregistered_addr, - &api, - cache, - &mock_router(), - &block, - info, - b"{}".to_vec(), - ) - }) + let err = transactional( + &mut wasm_storage, + |cache, _| { + // and the error for calling an unregistered contract + let info = mock_info(user_addr.as_str(), &[]); + wasm_keeper.call_instantiate( + unregistered_addr, + &api, + cache, + &mock_router(), + &block, + info, + b"{}".to_vec(), + ) + }, + || wasm_keeper.inc_transaction_index(), + ) .unwrap_err(); // Default error message from router when not found @@ -1697,132 +1751,144 @@ mod test { let payout1 = coin(100, "TGD"); // set contract 1 and commit (on router) - let contract1 = transactional(&mut wasm_storage, |cache, _| { - let contract = wasm_keeper - .register_contract( - &api, - cache, - code_id, - user_addr.clone(), - None, - "".to_string(), - 1000, - None, - ) - .unwrap(); - let info = mock_info(user_addr.as_str(), &[]); - let init_msg = to_json_vec(&payout::InstantiateMessage { - payout: payout1.clone(), - }) - .unwrap(); - wasm_keeper - .call_instantiate( - contract.clone(), - &api, - cache, - &mock_router(), - &block, - info, - init_msg, - ) + let contract1 = transactional( + &mut wasm_storage, + |cache, _| { + let contract = wasm_keeper + .register_contract( + &api, + cache, + code_id, + user_addr.clone(), + None, + "".to_string(), + 1000, + None, + ) + .unwrap(); + let info = mock_info(user_addr.as_str(), &[]); + let init_msg = to_json_vec(&payout::InstantiateMessage { + payout: payout1.clone(), + }) .unwrap(); + wasm_keeper + .call_instantiate( + contract.clone(), + &api, + cache, + &mock_router(), + &block, + info, + init_msg, + ) + .unwrap(); - Ok(contract) - }) + Ok(contract) + }, + || wasm_keeper.inc_transaction_index(), + ) .unwrap(); let payout2 = coin(50, "BTC"); let payout3 = coin(1234, "ATOM"); // create a new cache and check we can use contract 1 - let (contract2, contract3) = transactional(&mut wasm_storage, |cache, wasm_reader| { - assert_payout(&wasm_keeper, cache, &contract1, &payout1); + let (contract2, contract3) = transactional( + &mut wasm_storage, + |cache, wasm_reader| { + assert_payout(&wasm_keeper, cache, &contract1, &payout1); - // create contract 2 and use it - let contract2 = wasm_keeper - .register_contract( - &api, - cache, - code_id, - user_addr.clone(), - None, - "".to_owned(), - 1000, - None, - ) - .unwrap(); - let info = mock_info(user_addr.as_str(), &[]); - let init_msg = to_json_vec(&payout::InstantiateMessage { - payout: payout2.clone(), - }) - .unwrap(); - let _res = wasm_keeper - .call_instantiate( - contract2.clone(), - &api, - cache, - &mock_router(), - &block, - info, - init_msg, - ) - .unwrap(); - assert_payout(&wasm_keeper, cache, &contract2, &payout2); - - // create a level2 cache and check we can use contract 1 and contract 2 - let contract3 = transactional(cache, |cache2, read| { - assert_payout(&wasm_keeper, cache2, &contract1, &payout1); - assert_payout(&wasm_keeper, cache2, &contract2, &payout2); - - // create a contract on level 2 - let contract3 = wasm_keeper + // create contract 2 and use it + let contract2 = wasm_keeper .register_contract( &api, - cache2, + cache, code_id, - user_addr, + user_addr.clone(), None, "".to_owned(), 1000, None, ) .unwrap(); - let info = mock_info("johnny", &[]); + let info = mock_info(user_addr.as_str(), &[]); let init_msg = to_json_vec(&payout::InstantiateMessage { - payout: payout3.clone(), + payout: payout2.clone(), }) .unwrap(); let _res = wasm_keeper .call_instantiate( - contract3.clone(), + contract2.clone(), &api, - cache2, + cache, &mock_router(), &block, info, init_msg, ) .unwrap(); - assert_payout(&wasm_keeper, cache2, &contract3, &payout3); + assert_payout(&wasm_keeper, cache, &contract2, &payout2); - // ensure first cache still doesn't see this contract - assert_no_contract(read, &contract3); - Ok(contract3) - }) - .unwrap(); + // create a level2 cache and check we can use contract 1 and contract 2 + let contract3 = transactional( + cache, + |cache2, read| { + assert_payout(&wasm_keeper, cache2, &contract1, &payout1); + assert_payout(&wasm_keeper, cache2, &contract2, &payout2); + + // create a contract on level 2 + let contract3 = wasm_keeper + .register_contract( + &api, + cache2, + code_id, + user_addr, + None, + "".to_owned(), + 1000, + None, + ) + .unwrap(); + let info = mock_info("johnny", &[]); + let init_msg = to_json_vec(&payout::InstantiateMessage { + payout: payout3.clone(), + }) + .unwrap(); + let _res = wasm_keeper + .call_instantiate( + contract3.clone(), + &api, + cache2, + &mock_router(), + &block, + info, + init_msg, + ) + .unwrap(); + assert_payout(&wasm_keeper, cache2, &contract3, &payout3); + + // ensure first cache still doesn't see this contract + assert_no_contract(read, &contract3); + Ok(contract3) + }, + || wasm_keeper.inc_transaction_index(), + ) + .unwrap(); - // after applying transaction, all contracts present on cache - assert_payout(&wasm_keeper, cache, &contract1, &payout1); - assert_payout(&wasm_keeper, cache, &contract2, &payout2); - assert_payout(&wasm_keeper, cache, &contract3, &payout3); + // after applying transaction, all contracts present on cache + assert_payout(&wasm_keeper, cache, &contract1, &payout1); + assert_payout(&wasm_keeper, cache, &contract2, &payout2); + assert_payout(&wasm_keeper, cache, &contract3, &payout3); - // but not yet the root router - assert_no_contract(wasm_reader, &contract1); - assert_no_contract(wasm_reader, &contract2); - assert_no_contract(wasm_reader, &contract3); + // but not yet the root router + assert_no_contract(wasm_reader, &contract1); + assert_no_contract(wasm_reader, &contract2); + assert_no_contract(wasm_reader, &contract3); - Ok((contract2, contract3)) - }) + Ok((contract2, contract3)) + }, + || wasm_keeper.inc_transaction_index(), + ) .unwrap(); // ensure that it is now applied to the router diff --git a/tests/mod.rs b/tests/mod.rs index edf9910d..1c449ef8 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -6,6 +6,7 @@ mod test_app_builder; mod test_contract_storage; mod test_module; mod test_prefixed_storage; +mod test_transaction_info; mod test_wasm; mod test_contracts { @@ -13,7 +14,6 @@ mod test_contracts { pub mod counter { use cosmwasm_std::{ to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdError, - WasmMsg, }; use cw_multi_test::{Contract, ContractWrapper}; use cw_storage_plus::Item; @@ -46,7 +46,7 @@ mod test_contracts { deps: DepsMut, _env: Env, _info: MessageInfo, - _msg: WasmMsg, + _msg: Empty, ) -> Result { if let Some(mut counter) = COUNTER.may_load(deps.storage).unwrap() { counter += 1; diff --git a/tests/test_app_builder/mod.rs b/tests/test_app_builder/mod.rs index d2dcc5c2..098253cc 100644 --- a/tests/test_app_builder/mod.rs +++ b/tests/test_app_builder/mod.rs @@ -14,6 +14,7 @@ mod test_with_ibc; mod test_with_staking; mod test_with_stargate; mod test_with_storage; +mod test_with_transaction; mod test_with_wasm; const NO_MESSAGE: &str = ""; diff --git a/tests/test_app_builder/test_with_transaction.rs b/tests/test_app_builder/test_with_transaction.rs new file mode 100644 index 00000000..19667dcc --- /dev/null +++ b/tests/test_app_builder/test_with_transaction.rs @@ -0,0 +1,17 @@ +use cosmwasm_std::TransactionInfo; +use cw_multi_test::{no_init, AppBuilder}; + +#[test] +fn building_app_with_custom_transaction_info_should_work() { + // prepare custom transaction info + let transaction_info = TransactionInfo { index: 21 }; + + // build the application with custom transaction info + let app_builder = AppBuilder::default(); + let app = app_builder + .with_transaction(Some(transaction_info)) + .build(no_init); + + // index should be the same value as provided during initialization + assert_eq!(21, app.transaction_info().unwrap().index); +} diff --git a/tests/test_app_builder/test_with_wasm.rs b/tests/test_app_builder/test_with_wasm.rs index 390c8684..cbb451e2 100644 --- a/tests/test_app_builder/test_with_wasm.rs +++ b/tests/test_app_builder/test_with_wasm.rs @@ -1,7 +1,8 @@ -use crate::test_app_builder::MyKeeper; +use crate::test_app_builder::{MyKeeper, NO_MESSAGE}; use crate::test_contracts; use cosmwasm_std::{ - Addr, Api, Binary, BlockInfo, Empty, Querier, Record, Storage, WasmMsg, WasmQuery, + Addr, Api, Binary, BlockInfo, Empty, Querier, Record, Storage, TransactionInfo, WasmMsg, + WasmQuery, }; use cw_multi_test::error::{bail, AnyResult}; use cw_multi_test::{ @@ -165,3 +166,14 @@ fn compiling_with_wasm_keeper_should_work() { let app_builder = AppBuilder::default(); let _ = app_builder.with_wasm(WasmKeeper::default()).build(no_init); } + +#[test] +fn default_transaction_info_should_work() { + let wasm_keeper: Box> = + Box::new(MyWasmKeeper::new(NO_MESSAGE, NO_MESSAGE, NO_MESSAGE)); + assert_eq!(None, wasm_keeper.transaction_info()); + wasm_keeper.inc_transaction_index(); + assert_eq!(None, wasm_keeper.transaction_info()); + wasm_keeper.set_transaction_info(Some(TransactionInfo { index: 15 })); + assert_eq!(None, wasm_keeper.transaction_info()); +} diff --git a/tests/test_transaction_info/mod.rs b/tests/test_transaction_info/mod.rs new file mode 100644 index 00000000..870b702a --- /dev/null +++ b/tests/test_transaction_info/mod.rs @@ -0,0 +1 @@ +mod test_transaction_index; diff --git a/tests/test_transaction_info/test_transaction_index.rs b/tests/test_transaction_info/test_transaction_index.rs new file mode 100644 index 00000000..a7c07c57 --- /dev/null +++ b/tests/test_transaction_info/test_transaction_index.rs @@ -0,0 +1,105 @@ +use crate::test_contracts::counter; +use cosmwasm_std::{BlockInfo, Empty, Timestamp}; +use cw_multi_test::{App, Executor}; + +#[test] +fn default_transaction_index_should_be_none() { + let app = App::default(); + assert_eq!(None, app.transaction_info()); +} + +#[test] +fn instantiate_should_increment_transaction_index() { + let mut app = App::default(); + let sender_addr = app.api().addr_make("sender"); + let code_id = app.store_code(counter::contract()); + assert_eq!(None, app.transaction_info()); + app.instantiate_contract(code_id, sender_addr, &Empty {}, &[], "counter", None) + .unwrap(); + assert_eq!(0, app.transaction_info().unwrap().index); +} + +#[test] +fn execute_should_increment_transaction_index() { + let mut app = App::default(); + let sender_addr = app.api().addr_make("sender"); + let code_id = app.store_code(counter::contract()); + assert_eq!(None, app.transaction_info()); + let contract_addr = app + .instantiate_contract( + code_id, + sender_addr.clone(), + &Empty {}, + &[], + "counter", + None, + ) + .unwrap(); + assert_eq!(0, app.transaction_info().unwrap().index); + app.execute_contract(sender_addr, contract_addr, &Empty {}, &[]) + .unwrap(); + assert_eq!(1, app.transaction_info().unwrap().index); +} + +#[test] +fn setting_block_should_reset_transaction_index() { + let mut app = App::default(); + let sender_addr = app.api().addr_make("sender"); + let code_id = app.store_code(counter::contract()); + assert_eq!(None, app.transaction_info()); + let contract_addr = app + .instantiate_contract( + code_id, + sender_addr.clone(), + &Empty {}, + &[], + "counter", + None, + ) + .unwrap(); + assert_eq!(0, app.transaction_info().unwrap().index); + app.execute_contract(sender_addr.clone(), contract_addr.clone(), &Empty {}, &[]) + .unwrap(); + assert_eq!(1, app.transaction_info().unwrap().index); + // prepare custom block properties + let block = BlockInfo { + height: 20, + time: Timestamp::from_nanos(1_571_797_419_879_305_544), + chain_id: "my-testnet".to_string(), + }; + app.set_block(block); + assert_eq!(None, app.transaction_info()); + app.execute_contract(sender_addr, contract_addr, &Empty {}, &[]) + .unwrap(); + assert_eq!(0, app.transaction_info().unwrap().index); +} + +#[test] +fn updating_block_should_reset_transaction_index() { + let mut app = App::default(); + let sender_addr = app.api().addr_make("sender"); + let code_id = app.store_code(counter::contract()); + assert_eq!(None, app.transaction_info()); + let contract_addr = app + .instantiate_contract( + code_id, + sender_addr.clone(), + &Empty {}, + &[], + "counter", + None, + ) + .unwrap(); + assert_eq!(0, app.transaction_info().unwrap().index); + app.execute_contract(sender_addr.clone(), contract_addr.clone(), &Empty {}, &[]) + .unwrap(); + assert_eq!(1, app.transaction_info().unwrap().index); + app.update_block(|block| { + block.height += 1; + block.time = block.time.plus_seconds(60); + }); + assert_eq!(None, app.transaction_info()); + app.execute_contract(sender_addr, contract_addr, &Empty {}, &[]) + .unwrap(); + assert_eq!(0, app.transaction_info().unwrap().index); +}