diff --git a/.circleci/config.yml b/.circleci/config.yml index 9c08e76b05..764ec11f53 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -274,19 +274,19 @@ jobs: - run: name: Build library for native target (all features) working_directory: ~/project/packages/std - command: cargo build --locked --features abort,iterator,staking,stargate + command: cargo build --locked --features abort,iterator,staking,stargate,cosmwasm_1_1 - run: name: Build library for wasm target (all features) working_directory: ~/project/packages/std - command: cargo wasm --locked --features abort,iterator,staking,stargate + command: cargo wasm --locked --features abort,iterator,staking,stargate,cosmwasm_1_1 - run: name: Run unit tests (all features) working_directory: ~/project/packages/std - command: cargo test --locked --features abort,iterator,staking,stargate + command: cargo test --locked --features abort,iterator,staking,stargate,cosmwasm_1_1 - run: name: Build and run schema generator working_directory: ~/project/packages/std - command: cargo schema --locked + command: cargo schema --features cosmwasm_1_1 --locked - run: name: Ensure schemas are up-to-date command: | diff --git a/CHANGELOG.md b/CHANGELOG.md index e8ab7f3c49..4efffcf982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to on the other for all `Uint` and `Decimal` types - cosmwasm-std: Implement `saturating_add`/`sub`/`mul` for `Decimal`/`Decimal256`. +- cosmwasm-std: Implement `BankQuery::Supply` to allow querying the total supply + of a native token - cosmwasm-std: Implement `MIN` const value for all `Uint` and `Decimal` types - cosmwasm-std: Implement `checked_div_euclid` for `Uint256`/`Uint512` - cosmwasm-std: Add `QuerierWrapper::query_wasm_contract_info` - this is just a diff --git a/contracts/reflect/Cargo.toml b/contracts/reflect/Cargo.toml index 6e66eded1c..6ec3c92e4a 100644 --- a/contracts/reflect/Cargo.toml +++ b/contracts/reflect/Cargo.toml @@ -34,7 +34,7 @@ backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] [dependencies] cosmwasm-schema = { path = "../../packages/schema" } -cosmwasm-std = { path = "../../packages/std", default-features = false, features = ["staking", "stargate"] } +cosmwasm-std = { path = "../../packages/std", default-features = false, features = ["staking", "stargate", "cosmwasm_1_1"] } cosmwasm-storage = { path = "../../packages/storage", default-features = false } schemars = "0.8.1" serde = { version = "=1.0.103", default-features = false, features = ["derive"] } diff --git a/contracts/reflect/schema/reflect.json b/contracts/reflect/schema/reflect.json index a2b6c8ebd8..6ade7f08c8 100644 --- a/contracts/reflect/schema/reflect.json +++ b/contracts/reflect/schema/reflect.json @@ -976,6 +976,27 @@ "definitions": { "BankQuery": { "oneOf": [ + { + "description": "This calls into the native bank module for querying the total supply of one denomination. It does the same as the SupplyOf call in Cosmos SDK's RPC API. Return value is of type SupplyResponse.", + "type": "object", + "required": [ + "supply" + ], + "properties": { + "supply": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, { "description": "This calls into the native bank module for one denomination Return value is BalanceResponse", "type": "object", diff --git a/contracts/reflect/tests/integration.rs b/contracts/reflect/tests/integration.rs index 25bb93fe3c..4df7685dc8 100644 --- a/contracts/reflect/tests/integration.rs +++ b/contracts/reflect/tests/integration.rs @@ -18,8 +18,9 @@ //! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...) use cosmwasm_std::{ - coin, coins, from_binary, BankMsg, Binary, Coin, ContractResult, Event, Reply, Response, - StakingMsg, SubMsg, SubMsgResponse, SubMsgResult, SystemResult, + coin, coins, from_binary, BankMsg, BankQuery, Binary, Coin, ContractResult, Event, + QueryRequest, Reply, Response, StakingMsg, SubMsg, SubMsgResponse, SubMsgResult, + SupplyResponse, SystemResult, }; use cosmwasm_vm::{ testing::{ @@ -30,8 +31,8 @@ use cosmwasm_vm::{ }; use reflect::msg::{ - CapitalizedResponse, CustomMsg, ExecuteMsg, InstantiateMsg, OwnerResponse, QueryMsg, - SpecialQuery, + CapitalizedResponse, ChainResponse, CustomMsg, ExecuteMsg, InstantiateMsg, OwnerResponse, + QueryMsg, SpecialQuery, }; use reflect::testing::custom_query_execute; @@ -56,6 +57,19 @@ pub fn mock_dependencies_with_custom_querier( } } +pub fn mock_dependencies_with_custom_querier_and_balances( + balances: &[(&str, &[Coin])], +) -> Backend> { + let custom_querier: MockQuerier = MockQuerier::new(balances) + .with_custom_handler(|query| SystemResult::Ok(custom_query_execute(query))); + + Backend { + api: MockApi::default(), + storage: MockStorage::default(), + querier: custom_querier, + } +} + #[test] fn proper_initialization() { let mut deps = mock_instance(WASM, &[]); @@ -166,6 +180,34 @@ fn transfer_requires_owner() { assert!(msg.contains("Permission denied: the sender is not the current owner")); } +#[test] +fn supply_query() { + // stub gives us defaults. Consume it and override... + let custom = mock_dependencies_with_custom_querier_and_balances(&[ + ("ryan_reynolds", &[coin(5, "ATOM"), coin(10, "OSMO")]), + ("huge_ackman", &[coin(15, "OSMO"), coin(5, "BTC")]), + ]); + // we cannot use mock_instance, so we just copy and modify code from cosmwasm_vm::testing + let (instance_options, memory_limit) = mock_instance_options(); + let mut deps = Instance::from_code(WASM, custom, instance_options, memory_limit).unwrap(); + + // we don't even initialize, just trigger a query + let res = query( + &mut deps, + mock_env(), + QueryMsg::Chain { + request: QueryRequest::Bank(BankQuery::Supply { + denom: "OSMO".to_string(), + }), + }, + ) + .unwrap(); + + let res: ChainResponse = from_binary(&res).unwrap(); + let res: SupplyResponse = from_binary(&res.data).unwrap(); + assert_eq!(res.amount, coin(25, "OSMO")); +} + #[test] fn dispatch_custom_query() { // stub gives us defaults. Consume it and override... diff --git a/devtools/check_workspace.sh b/devtools/check_workspace.sh index 1c0d0bcaac..7e9c04d064 100755 --- a/devtools/check_workspace.sh +++ b/devtools/check_workspace.sh @@ -13,7 +13,7 @@ cargo fmt cargo wasm-debug cargo wasm-debug --features iterator,staking,stargate cargo clippy --all-targets --features iterator,staking,stargate -- -D warnings - cargo schema + cargo schema --features cosmwasm_1_1 ) (cd packages/storage && cargo build && cargo clippy --all-targets --features iterator -- -D warnings) (cd packages/schema && cargo build && cargo clippy --all-targets -- -D warnings) diff --git a/packages/check/src/main.rs b/packages/check/src/main.rs index 98541f5f8f..1d4e6eb67d 100644 --- a/packages/check/src/main.rs +++ b/packages/check/src/main.rs @@ -10,7 +10,7 @@ use colored::Colorize; use cosmwasm_vm::capabilities_from_csv; use cosmwasm_vm::internals::{check_wasm, compile}; -const DEFAULT_AVAILABLE_CAPABILITIES: &str = "iterator,staking,stargate"; +const DEFAULT_AVAILABLE_CAPABILITIES: &str = "iterator,staking,stargate,cosmwasm_1_1"; pub fn main() { let matches = App::new("Contract checking") diff --git a/packages/std/Cargo.toml b/packages/std/Cargo.toml index d3ed9ccd8d..e2b4478dd5 100644 --- a/packages/std/Cargo.toml +++ b/packages/std/Cargo.toml @@ -33,6 +33,9 @@ stargate = [] # ibc3 extends ibc messages with ibc-v3 only features. This should only be enabled on contracts # that require these types. Without this, they get the smaller ibc-v1 API. ibc3 = ["stargate"] +# This feature makes `BankQuery::Supply` available for the contract to call, but requires +# the host blockchain to run CosmWasm `1.1.0` or higher. +cosmwasm_1_1 = [] [dependencies] base64 = "0.13.0" diff --git a/packages/std/schema/query_request.json b/packages/std/schema/query_request.json index 6417cdaa51..fd674b44cf 100644 --- a/packages/std/schema/query_request.json +++ b/packages/std/schema/query_request.json @@ -42,6 +42,27 @@ "definitions": { "BankQuery": { "oneOf": [ + { + "description": "This calls into the native bank module for querying the total supply of one denomination. It does the same as the SupplyOf call in Cosmos SDK's RPC API. Return value is of type SupplyResponse.", + "type": "object", + "required": [ + "supply" + ], + "properties": { + "supply": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, { "description": "This calls into the native bank module for one denomination Return value is BalanceResponse", "type": "object", diff --git a/packages/std/src/exports.rs b/packages/std/src/exports.rs index 90f1880da4..a2c986806f 100644 --- a/packages/std/src/exports.rs +++ b/packages/std/src/exports.rs @@ -41,6 +41,10 @@ extern "C" fn requires_staking() -> () {} #[no_mangle] extern "C" fn requires_stargate() -> () {} +#[cfg(feature = "cosmwasm_1_1")] +#[no_mangle] +extern "C" fn requires_cosmwasm_1_1() -> () {} + /// interface_version_* exports mark which Wasm VM interface level this contract is compiled for. /// They can be checked by cosmwasm_vm. /// Update this whenever the Wasm VM interface breaks. diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index b3072e677b..54e2a4bce3 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -46,6 +46,8 @@ pub use crate::math::{ Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Isqrt, Uint128, Uint256, Uint512, Uint64, }; +#[cfg(feature = "cosmwasm_1_1")] +pub use crate::query::SupplyResponse; pub use crate::query::{ AllBalanceResponse, BalanceResponse, BankQuery, ContractInfoResponse, CustomQuery, QueryRequest, WasmQuery, diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs index 3b0db5c1c6..1feb040228 100644 --- a/packages/std/src/mock.rs +++ b/packages/std/src/mock.rs @@ -15,6 +15,9 @@ use crate::ibc::{ IbcEndpoint, IbcOrder, IbcPacket, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcTimeoutBlock, }; +use crate::math::Uint128; +#[cfg(feature = "cosmwasm_1_1")] +use crate::query::SupplyResponse; use crate::query::{ AllBalanceResponse, BalanceResponse, BankQuery, CustomQuery, QueryRequest, WasmQuery, }; @@ -452,7 +455,7 @@ impl MockQuerier { addr: impl Into, balance: Vec, ) -> Option> { - self.bank.balances.insert(addr.into(), balance) + self.bank.update_balance(addr, balance) } #[cfg(feature = "staking")] @@ -564,20 +567,68 @@ impl Default for WasmQuerier { #[derive(Clone, Default)] pub struct BankQuerier { + #[allow(dead_code)] + /// HashMap + supplies: HashMap, + /// HashMap balances: HashMap>, } impl BankQuerier { pub fn new(balances: &[(&str, &[Coin])]) -> Self { - let mut map = HashMap::new(); - for (addr, coins) in balances.iter() { - map.insert(addr.to_string(), coins.to_vec()); + let balances: HashMap<_, _> = balances + .iter() + .map(|(s, c)| (s.to_string(), c.to_vec())) + .collect(); + + BankQuerier { + supplies: Self::calculate_supplies(&balances), + balances, } - BankQuerier { balances: map } + } + + pub fn update_balance( + &mut self, + addr: impl Into, + balance: Vec, + ) -> Option> { + let result = self.balances.insert(addr.into(), balance); + self.supplies = Self::calculate_supplies(&self.balances); + + result + } + + fn calculate_supplies(balances: &HashMap>) -> HashMap { + let mut supplies = HashMap::new(); + + let all_coins = balances.iter().flat_map(|(_, coins)| coins); + + for coin in all_coins { + *supplies + .entry(coin.denom.clone()) + .or_insert_with(Uint128::zero) += coin.amount; + } + + supplies } pub fn query(&self, request: &BankQuery) -> QuerierResult { let contract_result: ContractResult = match request { + #[cfg(feature = "cosmwasm_1_1")] + BankQuery::Supply { denom } => { + let amount = self + .supplies + .get(denom) + .cloned() + .unwrap_or_else(Uint128::zero); + let bank_res = SupplyResponse { + amount: Coin { + amount, + denom: denom.to_string(), + }, + }; + to_binary(&bank_res).into() + } BankQuery::Balance { address, denom } => { // proper error on not found, serialize result on found let amount = self @@ -1060,6 +1111,46 @@ mod tests { assert_eq!(res.unwrap_err(), VerificationError::InvalidPubkeyFormat); } + #[cfg(feature = "cosmwasm_1_1")] + #[test] + fn bank_querier_supply() { + let addr1 = String::from("foo"); + let balance1 = vec![coin(123, "ELF"), coin(777, "FLY")]; + + let addr2 = String::from("bar"); + let balance2 = coins(321, "ELF"); + + let bank = BankQuerier::new(&[(&addr1, &balance1), (&addr2, &balance2)]); + + let elf = bank + .query(&BankQuery::Supply { + denom: "ELF".to_string(), + }) + .unwrap() + .unwrap(); + let res: SupplyResponse = from_binary(&elf).unwrap(); + assert_eq!(res.amount, coin(444, "ELF")); + + let fly = bank + .query(&BankQuery::Supply { + denom: "FLY".to_string(), + }) + .unwrap() + .unwrap(); + let res: SupplyResponse = from_binary(&fly).unwrap(); + assert_eq!(res.amount, coin(777, "FLY")); + + // if a denom does not exist, should return zero amount, instead of throwing an error + let atom = bank + .query(&BankQuery::Supply { + denom: "ATOM".to_string(), + }) + .unwrap() + .unwrap(); + let res: SupplyResponse = from_binary(&atom).unwrap(); + assert_eq!(res.amount, coin(0, "ATOM")); + } + #[test] fn bank_querier_all_balances() { let addr = String::from("foobar"); diff --git a/packages/std/src/query/bank.rs b/packages/std/src/query/bank.rs index 51f274d1f8..9656ea6138 100644 --- a/packages/std/src/query/bank.rs +++ b/packages/std/src/query/bank.rs @@ -7,6 +7,11 @@ use crate::Coin; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum BankQuery { + /// This calls into the native bank module for querying the total supply of one denomination. + /// It does the same as the SupplyOf call in Cosmos SDK's RPC API. + /// Return value is of type SupplyResponse. + #[cfg(feature = "cosmwasm_1_1")] + Supply { denom: String }, /// This calls into the native bank module for one denomination /// Return value is BalanceResponse Balance { address: String, denom: String }, @@ -16,6 +21,16 @@ pub enum BankQuery { AllBalances { address: String }, } +#[cfg(feature = "cosmwasm_1_1")] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub struct SupplyResponse { + /// Always returns a Coin with the requested denom. + /// This will be of zero amount if the denom does not exist. + pub amount: Coin, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct BalanceResponse { diff --git a/packages/std/src/query/mod.rs b/packages/std/src/query/mod.rs index 8293734bd8..67a2fcdb0d 100644 --- a/packages/std/src/query/mod.rs +++ b/packages/std/src/query/mod.rs @@ -10,6 +10,8 @@ mod ibc; mod staking; mod wasm; +#[cfg(feature = "cosmwasm_1_1")] +pub use bank::SupplyResponse; pub use bank::{AllBalanceResponse, BalanceResponse, BankQuery}; #[cfg(feature = "stargate")] pub use ibc::{ChannelResponse, IbcQuery, ListChannelsResponse, PortIdResponse}; diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 0e16023180..6536d75bde 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -8,6 +8,8 @@ use crate::coins::Coin; use crate::errors::{RecoverPubkeyError, StdError, StdResult, VerificationError}; #[cfg(feature = "iterator")] use crate::iterator::{Order, Record}; +#[cfg(feature = "cosmwasm_1_1")] +use crate::query::SupplyResponse; use crate::query::{ AllBalanceResponse, BalanceResponse, BankQuery, CustomQuery, QueryRequest, WasmQuery, }; @@ -198,6 +200,16 @@ impl<'a, C: CustomQuery> QuerierWrapper<'a, C> { } } + #[cfg(feature = "cosmwasm_1_1")] + pub fn query_supply(&self, denom: impl Into) -> StdResult { + let request = BankQuery::Supply { + denom: denom.into(), + } + .into(); + let res: SupplyResponse = self.query(&request)?; + Ok(res.amount) + } + pub fn query_balance( &self, address: impl Into, @@ -385,6 +397,27 @@ mod tests { assert_eq!(balance.amount.amount, Uint128::new(5)); } + #[cfg(feature = "cosmwasm_1_1")] + #[test] + fn bank_query_helpers_work() { + use crate::coin; + + let querier: MockQuerier = MockQuerier::new(&[ + ("foo", &[coin(123, "ELF"), coin(777, "FLY")]), + ("bar", &[coin(321, "ELF")]), + ]); + let wrapper = QuerierWrapper::::new(&querier); + + let supply = wrapper.query_supply("ELF").unwrap(); + assert_eq!(supply, coin(444, "ELF")); + + let balance = wrapper.query_balance("foo", "ELF").unwrap(); + assert_eq!(balance, coin(123, "ELF")); + + let all_balances = wrapper.query_all_balances("foo").unwrap(); + assert_eq!(all_balances, vec![coin(123, "ELF"), coin(777, "FLY")]); + } + #[test] fn contract_info() { const ACCT: &str = "foobar"; diff --git a/packages/vm/examples/check_contract.rs b/packages/vm/examples/check_contract.rs index 401421cd7b..1d53b164ee 100644 --- a/packages/vm/examples/check_contract.rs +++ b/packages/vm/examples/check_contract.rs @@ -6,7 +6,7 @@ use clap::{App, Arg}; use cosmwasm_vm::capabilities_from_csv; use cosmwasm_vm::internals::{check_wasm, compile}; -const DEFAULT_AVAILABLE_CAPABILITIES: &str = "iterator,staking,stargate"; +const DEFAULT_AVAILABLE_CAPABILITIES: &str = "iterator,staking,stargate,cosmwasm_1_1"; pub fn main() { eprintln!("`check_contract` will be removed from the next version of `cosmwasm-vm` - please use `cosmwasm-check` instead."); diff --git a/packages/vm/src/testing/instance.rs b/packages/vm/src/testing/instance.rs index a30030a44b..a5aeeb6ac6 100644 --- a/packages/vm/src/testing/instance.rs +++ b/packages/vm/src/testing/instance.rs @@ -95,7 +95,7 @@ pub struct MockInstanceOptions<'a> { impl MockInstanceOptions<'_> { fn default_capabilities() -> HashSet { #[allow(unused_mut)] - let mut out = capabilities_from_csv("iterator,staking"); + let mut out = capabilities_from_csv("iterator,staking,cosmwasm_1_1"); #[cfg(feature = "stargate")] out.insert("stargate".to_string()); out