diff --git a/.circleci/config.yml b/.circleci/config.yml index 0335b7ae71..60bd628780 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -356,15 +356,15 @@ jobs: - run: name: Build library for native target (all features) working_directory: ~/project/packages/std - command: cargo build --locked --features abort,iterator,staking,stargate,cosmwasm_1_2 + command: cargo build --locked --features abort,iterator,staking,stargate,cosmwasm_1_3 - run: name: Build library for wasm target (all features) working_directory: ~/project/packages/std - command: cargo wasm --locked --features abort,iterator,staking,stargate,cosmwasm_1_2 + command: cargo wasm --locked --features abort,iterator,staking,stargate,cosmwasm_1_3 - run: name: Run unit tests (all features) working_directory: ~/project/packages/std - command: cargo test --locked --features abort,iterator,staking,stargate,cosmwasm_1_2 + command: cargo test --locked --features abort,iterator,staking,stargate,cosmwasm_1_3 - save_cache: paths: - /usr/local/cargo/registry @@ -907,7 +907,7 @@ jobs: - run: name: Clippy linting on std (all feature flags) working_directory: ~/project/packages/std - command: cargo clippy --all-targets --features abort,iterator,staking,stargate,cosmwasm_1_2 -- -D warnings + command: cargo clippy --all-targets --features abort,iterator,staking,stargate,cosmwasm_1_3 -- -D warnings - run: name: Clippy linting on storage (no feature flags) working_directory: ~/project/packages/storage @@ -984,7 +984,7 @@ jobs: CRYPTO=" cargo tarpaulin --skip-clean --out Xml --output-dir reports/crypto --packages cosmwasm-crypto" DERIVE=" cargo tarpaulin --skip-clean --out Xml --output-dir reports/derive --packages cosmwasm-derive" SCHEMA=" cargo tarpaulin --skip-clean --out Xml --output-dir reports/schema --packages cosmwasm-schema" - STD=" cargo tarpaulin --skip-clean --out Xml --output-dir reports/std --packages cosmwasm-std --features abort,iterator,staking,stargate,cosmwasm_1_2" + STD=" cargo tarpaulin --skip-clean --out Xml --output-dir reports/std --packages cosmwasm-std --features abort,iterator,staking,stargate,cosmwasm_1_3" STORAGE="cargo tarpaulin --skip-clean --out Xml --output-dir reports/storage --packages cosmwasm-storage" docker run --security-opt seccomp=unconfined -v "${PWD}:/volume" xd009642/tarpaulin:0.21.0 \ sh -c "$CRYPTO && $DERIVE && $SCHEMA && $STD && $STORAGE" diff --git a/.vscode/settings.json b/.vscode/settings.json index 4fc9b25774..3105948a84 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.cargo.features": ["abort", "stargate", "staking", "cosmwasm_1_2"] + "rust-analyzer.cargo.features": ["abort", "stargate", "staking", "cosmwasm_1_3"] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7380f7fb7a..155f6f08f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,19 @@ and this project adheres to ### Added +- cosmwasm-std: Implement `BankQuery::AllDenomMetadata` to allow querying all + the denom metadata and `BankQuery::DenomMetadata` to query a specific one. In + order to use this query in a contract, the `cosmwasm_1_3` feature needs to be + enabled for the `cosmwasm_std` dependency. This makes the contract + incompatible with chains running anything lower than CosmWasm `1.3.0`. + ([#1647]) - cosmwasm-vm: Add `Cache::save_wasm_unchecked` to save Wasm blobs that have been checked before. This is useful for state-sync where we know the Wasm code was checked when it was first uploaded. ([#1635]) - cosmwasm-std: Add `FromStr` impl for `Coin`. ([#1684]) [#1635]: https://github.com/CosmWasm/cosmwasm/pull/1635 +[#1647]: https://github.com/CosmWasm/cosmwasm/pull/1647 [#1684]: https://github.com/CosmWasm/cosmwasm/pull/1684 ### Changed diff --git a/MIGRATING.md b/MIGRATING.md index 0000f999fc..ac9d8dd347 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -4,6 +4,33 @@ This guide explains what is needed to upgrade contracts when migrating over major releases of `cosmwasm`. Note that you can also view the [complete CHANGELOG](./CHANGELOG.md) to understand the differences. +## 1.2.x -> 1.3.0 + +- Update `cosmwasm-*` dependencies in Cargo.toml (skip the ones you don't use): + + ``` + [dependencies] + cosmwasm-std = "1.3.0" + cosmwasm-storage = "1.3.0" + # ... + + [dev-dependencies] + cosmwasm-schema = "1.3.0" + cosmwasm-vm = "1.3.0" + # ... + ``` + +- If you want to use a feature that is only available on CosmWasm 1.3+ chains, + use this feature: + + ```diff + -cosmwasm-std = { version = "1.3.0", features = ["stargate"] } + +cosmwasm-std = { version = "1.3.0", features = ["stargate", "cosmwasm_1_3"] } + ``` + + Please note that `cosmwasm_1_2` implies `cosmwasm_1_1`, and `cosmwasm_1_3` + implies `cosmwasm_1_2`, and so on, so there is no need to set multiple. + ## 1.1.x -> 1.2.0 - Update `cosmwasm-*` dependencies in Cargo.toml (skip the ones you don't use): diff --git a/contracts/cyberpunk/Cargo.toml b/contracts/cyberpunk/Cargo.toml index 67efa29d00..c2db096f28 100644 --- a/contracts/cyberpunk/Cargo.toml +++ b/contracts/cyberpunk/Cargo.toml @@ -30,7 +30,7 @@ backtraces = ["cosmwasm-std/backtraces", "cosmwasm-vm/backtraces"] [dependencies] cosmwasm-schema = { path = "../../packages/schema" } -cosmwasm-std = { path = "../../packages/std", default-features = false, features = ["abort"] } +cosmwasm-std = { path = "../../packages/std", default-features = false, features = ["abort", "cosmwasm_1_3"] } rust-argon2 = "0.8" thiserror = "1.0.26" diff --git a/contracts/cyberpunk/schema/cyberpunk.json b/contracts/cyberpunk/schema/cyberpunk.json index dcce96d609..ccb550b1ad 100644 --- a/contracts/cyberpunk/schema/cyberpunk.json +++ b/contracts/cyberpunk/schema/cyberpunk.json @@ -199,12 +199,198 @@ } }, "additionalProperties": false + }, + { + "description": "Queries `AllDenomMetadata` from the bank module repeatedly and returns all entries", + "type": "object", + "required": [ + "denoms" + ], + "properties": { + "denoms": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Queries `DenomMetadata` from the bank module and returns the result", + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, "migrate": null, "sudo": null, "responses": { + "denom": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DenomMetadata", + "description": "Replicates the cosmos-sdk bank module Metadata type", + "type": "object", + "required": [ + "base", + "denom_units", + "description", + "display", + "name", + "symbol", + "uri", + "uri_hash" + ], + "properties": { + "base": { + "type": "string" + }, + "denom_units": { + "type": "array", + "items": { + "$ref": "#/definitions/DenomUnit" + } + }, + "description": { + "type": "string" + }, + "display": { + "type": "string" + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "uri_hash": { + "type": "string" + } + }, + "definitions": { + "DenomUnit": { + "description": "Replicates the cosmos-sdk bank module DenomUnit type", + "type": "object", + "required": [ + "aliases", + "denom", + "exponent" + ], + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "denom": { + "type": "string" + }, + "exponent": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } + }, + "denoms": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_DenomMetadata", + "type": "array", + "items": { + "$ref": "#/definitions/DenomMetadata" + }, + "definitions": { + "DenomMetadata": { + "description": "Replicates the cosmos-sdk bank module Metadata type", + "type": "object", + "required": [ + "base", + "denom_units", + "description", + "display", + "name", + "symbol", + "uri", + "uri_hash" + ], + "properties": { + "base": { + "type": "string" + }, + "denom_units": { + "type": "array", + "items": { + "$ref": "#/definitions/DenomUnit" + } + }, + "description": { + "type": "string" + }, + "display": { + "type": "string" + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "uri_hash": { + "type": "string" + } + } + }, + "DenomUnit": { + "description": "Replicates the cosmos-sdk bank module DenomUnit type", + "type": "object", + "required": [ + "aliases", + "denom", + "exponent" + ], + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "denom": { + "type": "string" + }, + "exponent": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } + }, "mirror_env": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Env", diff --git a/contracts/cyberpunk/schema/raw/query.json b/contracts/cyberpunk/schema/raw/query.json index 18c07a5622..47b493442f 100644 --- a/contracts/cyberpunk/schema/raw/query.json +++ b/contracts/cyberpunk/schema/raw/query.json @@ -15,6 +15,42 @@ } }, "additionalProperties": false + }, + { + "description": "Queries `AllDenomMetadata` from the bank module repeatedly and returns all entries", + "type": "object", + "required": [ + "denoms" + ], + "properties": { + "denoms": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Queries `DenomMetadata` from the bank module and returns the result", + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] } diff --git a/contracts/cyberpunk/schema/raw/response_to_denom.json b/contracts/cyberpunk/schema/raw/response_to_denom.json new file mode 100644 index 0000000000..b541da804b --- /dev/null +++ b/contracts/cyberpunk/schema/raw/response_to_denom.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DenomMetadata", + "description": "Replicates the cosmos-sdk bank module Metadata type", + "type": "object", + "required": [ + "base", + "denom_units", + "description", + "display", + "name", + "symbol", + "uri", + "uri_hash" + ], + "properties": { + "base": { + "type": "string" + }, + "denom_units": { + "type": "array", + "items": { + "$ref": "#/definitions/DenomUnit" + } + }, + "description": { + "type": "string" + }, + "display": { + "type": "string" + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "uri_hash": { + "type": "string" + } + }, + "definitions": { + "DenomUnit": { + "description": "Replicates the cosmos-sdk bank module DenomUnit type", + "type": "object", + "required": [ + "aliases", + "denom", + "exponent" + ], + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "denom": { + "type": "string" + }, + "exponent": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } +} diff --git a/contracts/cyberpunk/schema/raw/response_to_denoms.json b/contracts/cyberpunk/schema/raw/response_to_denoms.json new file mode 100644 index 0000000000..e8fcd9a16e --- /dev/null +++ b/contracts/cyberpunk/schema/raw/response_to_denoms.json @@ -0,0 +1,78 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_DenomMetadata", + "type": "array", + "items": { + "$ref": "#/definitions/DenomMetadata" + }, + "definitions": { + "DenomMetadata": { + "description": "Replicates the cosmos-sdk bank module Metadata type", + "type": "object", + "required": [ + "base", + "denom_units", + "description", + "display", + "name", + "symbol", + "uri", + "uri_hash" + ], + "properties": { + "base": { + "type": "string" + }, + "denom_units": { + "type": "array", + "items": { + "$ref": "#/definitions/DenomUnit" + } + }, + "description": { + "type": "string" + }, + "display": { + "type": "string" + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + }, + "uri": { + "type": "string" + }, + "uri_hash": { + "type": "string" + } + } + }, + "DenomUnit": { + "description": "Replicates the cosmos-sdk bank module DenomUnit type", + "type": "object", + "required": [ + "aliases", + "denom", + "exponent" + ], + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "denom": { + "type": "string" + }, + "exponent": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + } +} diff --git a/contracts/cyberpunk/src/contract.rs b/contracts/cyberpunk/src/contract.rs index 55acddaf6b..7de7d80fd2 100644 --- a/contracts/cyberpunk/src/contract.rs +++ b/contracts/cyberpunk/src/contract.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - entry_point, to_binary, Api, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Response, - StdError, StdResult, WasmMsg, + entry_point, to_binary, Api, DenomMetadata, Deps, DepsMut, Empty, Env, MessageInfo, + PageRequest, QueryResponse, Response, StdError, StdResult, WasmMsg, }; use crate::errors::ContractError; @@ -179,11 +179,13 @@ fn execute_debug(api: &dyn Api) -> Result { } #[entry_point] -pub fn query(_deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { use QueryMsg::*; match msg { MirrorEnv {} => to_binary(&query_mirror_env(env)), + Denoms {} => to_binary(&query_denoms(deps)?), + Denom { denom } => to_binary(&query_denom(deps, denom)?), } } @@ -191,13 +193,40 @@ fn query_mirror_env(env: Env) -> Env { env } +fn query_denoms(deps: Deps) -> StdResult> { + const PAGE_SIZE: u32 = 10; + let mut next_key = None; + let mut all_metadata = Vec::new(); + loop { + let page = deps.querier.query_all_denom_metadata(PageRequest { + key: next_key, + limit: PAGE_SIZE, + reverse: false, + })?; + + let len = page.metadata.len() as u32; + all_metadata.extend(page.metadata.into_iter()); + next_key = page.next_key; + + if next_key.is_none() || len < PAGE_SIZE { + break; + } + } + + Ok(all_metadata) +} + +fn query_denom(deps: Deps, denom: String) -> StdResult { + deps.querier.query_denom_metadata(denom) +} + #[cfg(test)] mod tests { use super::*; use cosmwasm_std::testing::{ mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; - use cosmwasm_std::OwnedDeps; + use cosmwasm_std::{from_binary, DenomMetadata, DenomUnit, OwnedDeps}; fn setup() -> OwnedDeps { let mut deps = mock_dependencies(); @@ -220,4 +249,47 @@ mod tests { let msg = ExecuteMsg::Debug {}; execute(deps.as_mut(), mock_env(), mock_info("caller", &[]), msg).unwrap(); } + + #[test] + fn query_denoms_works() { + let mut deps = setup(); + + deps.querier.set_denom_metadata( + &(0..98) + .map(|i| DenomMetadata { + symbol: format!("FOO{i}"), + name: "Foo".to_string(), + description: "Foo coin".to_string(), + denom_units: vec![DenomUnit { + denom: "ufoo".to_string(), + exponent: 8, + aliases: vec!["microfoo".to_string(), "foobar".to_string()], + }], + display: "FOO".to_string(), + base: format!("ufoo{i}"), + uri: "https://foo.bar".to_string(), + uri_hash: "foo".to_string(), + }) + .collect::>(), + ); + + let symbols: Vec = + from_binary(&query(deps.as_ref(), mock_env(), QueryMsg::Denoms {}).unwrap()).unwrap(); + + assert_eq!(symbols.len(), 98); + + let denom: DenomMetadata = from_binary( + &query( + deps.as_ref(), + mock_env(), + QueryMsg::Denom { + denom: "ufoo0".to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + + assert_eq!(denom.symbol, "FOO0"); + } } diff --git a/contracts/cyberpunk/src/msg.rs b/contracts/cyberpunk/src/msg.rs index 7abb882009..8e9ee80bf6 100644 --- a/contracts/cyberpunk/src/msg.rs +++ b/contracts/cyberpunk/src/msg.rs @@ -1,4 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::DenomMetadata; #[cw_serde] pub enum ExecuteMsg { @@ -38,4 +39,12 @@ pub enum QueryMsg { /// Returns the env for testing #[returns(cosmwasm_std::Env)] MirrorEnv {}, + + /// Queries `AllDenomMetadata` from the bank module repeatedly and returns all entries + #[returns(Vec)] + Denoms {}, + + /// Queries `DenomMetadata` from the bank module and returns the result + #[returns(DenomMetadata)] + Denom { denom: String }, } diff --git a/docs/CAPABILITIES-BUILT-IN.md b/docs/CAPABILITIES-BUILT-IN.md index 8fdd679fe1..d822971807 100644 --- a/docs/CAPABILITIES-BUILT-IN.md +++ b/docs/CAPABILITIES-BUILT-IN.md @@ -15,3 +15,6 @@ might define others. CosmWasm `1.1.0` or higher support this. - `cosmwasm_1_2` enables the `GovMsg::VoteWeighted` and `WasmMsg::Instantiate2` messages. Only chains running CosmWasm `1.2.0` or higher support this. +- `cosmwasm_1_3` enables the `BankQuery::AllDenomMetadata` and + `BankQuery::DenomMetadata` queries. Only chains running CosmWasm `1.3.0` or + higher support this. diff --git a/docs/USING_COSMWASM_STD.md b/docs/USING_COSMWASM_STD.md index 7da8fa6e92..36730f9945 100644 --- a/docs/USING_COSMWASM_STD.md +++ b/docs/USING_COSMWASM_STD.md @@ -44,6 +44,7 @@ The libarary comes with the following features: | backtraces | | Add backtraces to errors (for unit testing) | | cosmwasm_1_1 | | Features that require CosmWasm 1.1+ on the chain | | cosmwasm_1_2 | | Features that require CosmWasm 1.2+ on the chain | +| cosmwasm_1_3 | | Features that require CosmWasm 1.3+ on the chain | ## The cosmwasm-std dependency for contract developers diff --git a/packages/check/src/main.rs b/packages/check/src/main.rs index 201aaa8b92..561992bb28 100644 --- a/packages/check/src/main.rs +++ b/packages/check/src/main.rs @@ -10,7 +10,8 @@ use colored::Colorize; use cosmwasm_vm::capabilities_from_csv; use cosmwasm_vm::internals::{check_wasm, compile}; -const DEFAULT_AVAILABLE_CAPABILITIES: &str = "iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2"; +const DEFAULT_AVAILABLE_CAPABILITIES: &str = + "iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3"; pub fn main() { let matches = Command::new("Contract checking") diff --git a/packages/std/Cargo.toml b/packages/std/Cargo.toml index 2629690783..cb40abdb5d 100644 --- a/packages/std/Cargo.toml +++ b/packages/std/Cargo.toml @@ -9,7 +9,7 @@ license = "Apache-2.0" readme = "README.md" [package.metadata.docs.rs] -features = ["abort", "stargate", "staking", "ibc3", "cosmwasm_1_2"] +features = ["abort", "stargate", "staking", "ibc3", "cosmwasm_1_3"] [features] default = ["iterator", "abort"] @@ -39,6 +39,9 @@ cosmwasm_1_1 = [] # This feature makes `GovMsg::VoteWeighted` available for the contract to call, but requires # the host blockchain to run CosmWasm `1.2.0` or higher. cosmwasm_1_2 = ["cosmwasm_1_1"] +# This feature makes `BankQuery::DenomMetadata` available for the contract to call, but requires +# the host blockchain to run CosmWasm `1.3.0` or higher. +cosmwasm_1_3 = ["cosmwasm_1_2"] [dependencies] base64 = "0.13.0" diff --git a/packages/std/src/exports.rs b/packages/std/src/exports.rs index 5b4b30925c..27c46768dc 100644 --- a/packages/std/src/exports.rs +++ b/packages/std/src/exports.rs @@ -49,6 +49,10 @@ extern "C" fn requires_cosmwasm_1_1() -> () {} #[no_mangle] extern "C" fn requires_cosmwasm_1_2() -> () {} +#[cfg(feature = "cosmwasm_1_3")] +#[no_mangle] +extern "C" fn requires_cosmwasm_1_3() -> () {} + /// 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/iterator.rs b/packages/std/src/iterator.rs index e747581606..00190aa5ca 100644 --- a/packages/std/src/iterator.rs +++ b/packages/std/src/iterator.rs @@ -6,7 +6,7 @@ use crate::errors::StdError; /// allows contracts to reuse the type when deserializing database records. pub type Record> = (Vec, V); -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] // We assign these to integers to provide a stable API for passing over FFI (to wasm and Go) pub enum Order { Ascending = 1, diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index dca5b3ba83..d2e1568836 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -17,7 +17,9 @@ mod import_helpers; #[cfg(feature = "iterator")] mod iterator; mod math; +mod metadata; mod never; +mod pagination; mod panic; mod query; mod results; @@ -56,7 +58,9 @@ pub use crate::math::{ Decimal, Decimal256, Decimal256RangeExceeded, DecimalRangeExceeded, Fraction, Isqrt, Uint128, Uint256, Uint512, Uint64, }; +pub use crate::metadata::{DenomMetadata, DenomUnit}; pub use crate::never::Never; +pub use crate::pagination::PageRequest; #[cfg(feature = "cosmwasm_1_2")] pub use crate::query::CodeInfoResponse; #[cfg(feature = "cosmwasm_1_1")] diff --git a/packages/std/src/metadata.rs b/packages/std/src/metadata.rs new file mode 100644 index 0000000000..c0827ce722 --- /dev/null +++ b/packages/std/src/metadata.rs @@ -0,0 +1,23 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Replicates the cosmos-sdk bank module Metadata type +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema)] +pub struct DenomMetadata { + pub description: String, + pub denom_units: Vec, + pub base: String, + pub display: String, + pub name: String, + pub symbol: String, + pub uri: String, + pub uri_hash: String, +} + +/// Replicates the cosmos-sdk bank module DenomUnit type +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema)] +pub struct DenomUnit { + pub denom: String, + pub exponent: u32, + pub aliases: Vec, +} diff --git a/packages/std/src/pagination.rs b/packages/std/src/pagination.rs new file mode 100644 index 0000000000..d9d1d8ef96 --- /dev/null +++ b/packages/std/src/pagination.rs @@ -0,0 +1,12 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::Binary; + +/// Simplified version of the PageRequest type for pagination from the cosmos-sdk +#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, JsonSchema)] +pub struct PageRequest { + pub key: Option, + pub limit: u32, + pub reverse: bool, +} diff --git a/packages/std/src/query/bank.rs b/packages/std/src/query/bank.rs index b3a43e11d5..74d8b2b9d7 100644 --- a/packages/std/src/query/bank.rs +++ b/packages/std/src/query/bank.rs @@ -3,6 +3,9 @@ use serde::{Deserialize, Serialize}; use crate::Coin; +#[cfg(feature = "cosmwasm_1_3")] +use crate::{Binary, DenomMetadata, PageRequest}; + use super::query_response::QueryResponseType; #[non_exhaustive] @@ -21,6 +24,14 @@ pub enum BankQuery { /// Note that this may be much more expensive than Balance and should be avoided if possible. /// Return value is AllBalanceResponse. AllBalances { address: String }, + /// This calls into the native bank module for querying metadata for a specific bank token. + /// Return value is DenomMetadataResponse + #[cfg(feature = "cosmwasm_1_3")] + DenomMetadata { denom: String }, + /// This calls into the native bank module for querying metadata for all bank tokens that have a metadata entry. + /// Return value is AllDenomMetadataResponse + #[cfg(feature = "cosmwasm_1_3")] + AllDenomMetadata { pagination: Option }, } #[cfg(feature = "cosmwasm_1_1")] @@ -54,3 +65,28 @@ pub struct AllBalanceResponse { } impl QueryResponseType for AllBalanceResponse {} + +#[cfg(feature = "cosmwasm_1_3")] +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub struct DenomMetadataResponse { + /// The metadata for the queried denom. + pub metadata: DenomMetadata, +} + +#[cfg(feature = "cosmwasm_1_3")] +impl QueryResponseType for DenomMetadataResponse {} + +#[cfg(feature = "cosmwasm_1_3")] +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub struct AllDenomMetadataResponse { + /// Always returns metadata for all token denoms on the base chain. + pub metadata: Vec, + pub next_key: Option, +} + +#[cfg(feature = "cosmwasm_1_3")] +impl QueryResponseType for AllDenomMetadataResponse {} diff --git a/packages/std/src/query/mod.rs b/packages/std/src/query/mod.rs index 50061df091..c88e209b8f 100644 --- a/packages/std/src/query/mod.rs +++ b/packages/std/src/query/mod.rs @@ -14,6 +14,8 @@ mod wasm; #[cfg(feature = "cosmwasm_1_1")] pub use bank::SupplyResponse; pub use bank::{AllBalanceResponse, BalanceResponse, BankQuery}; +#[cfg(feature = "cosmwasm_1_3")] +pub use bank::{AllDenomMetadataResponse, DenomMetadataResponse}; #[cfg(feature = "stargate")] pub use ibc::{ChannelResponse, IbcQuery, ListChannelsResponse, PortIdResponse}; #[cfg(feature = "staking")] diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs index d219236578..3b5ce6f253 100644 --- a/packages/std/src/testing/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -1,8 +1,10 @@ use serde::de::DeserializeOwned; #[cfg(feature = "stargate")] use serde::Serialize; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::marker::PhantomData; +#[cfg(feature = "cosmwasm_1_3")] +use std::ops::Bound; use crate::addresses::{Addr, CanonicalAddr}; use crate::binary::Binary; @@ -32,7 +34,12 @@ use crate::storage::MemoryStorage; use crate::timestamp::Timestamp; use crate::traits::{Api, Querier, QuerierResult}; use crate::types::{BlockInfo, ContractInfo, Env, MessageInfo, TransactionInfo}; -use crate::Attribute; +#[cfg(feature = "cosmwasm_1_3")] +use crate::{ + query::{AllDenomMetadataResponse, DenomMetadataResponse}, + PageRequest, +}; +use crate::{Attribute, DenomMetadata}; #[cfg(feature = "stargate")] use crate::{ChannelResponse, IbcQuery, ListChannelsResponse, PortIdResponse}; @@ -473,6 +480,10 @@ impl MockQuerier { self.bank.update_balance(addr, balance) } + pub fn set_denom_metadata(&mut self, denom_metadata: &[DenomMetadata]) { + self.bank.set_denom_metadata(denom_metadata); + } + #[cfg(feature = "staking")] pub fn update_staking( &mut self, @@ -599,6 +610,8 @@ pub struct BankQuerier { supplies: HashMap, /// HashMap balances: HashMap>, + /// Vec + denom_metadata: BTreeMap, DenomMetadata>, } impl BankQuerier { @@ -611,6 +624,7 @@ impl BankQuerier { BankQuerier { supplies: Self::calculate_supplies(&balances), balances, + denom_metadata: BTreeMap::new(), } } @@ -625,6 +639,13 @@ impl BankQuerier { result } + pub fn set_denom_metadata(&mut self, denom_metadata: &[DenomMetadata]) { + self.denom_metadata = denom_metadata + .iter() + .map(|d| (d.base.as_bytes().to_vec(), d.clone())) + .collect(); + } + fn calculate_supplies(balances: &HashMap>) -> HashMap { let mut supplies = HashMap::new(); @@ -678,6 +699,59 @@ impl BankQuerier { }; to_binary(&bank_res).into() } + #[cfg(feature = "cosmwasm_1_3")] + BankQuery::DenomMetadata { denom } => { + let denom_metadata = self.denom_metadata.get(denom.as_bytes()); + match denom_metadata { + Some(m) => { + let metadata_res = DenomMetadataResponse { + metadata: m.clone(), + }; + to_binary(&metadata_res).into() + } + None => return SystemResult::Err(SystemError::Unknown {}), + } + } + #[cfg(feature = "cosmwasm_1_3")] + BankQuery::AllDenomMetadata { pagination } => { + let default_pagination = PageRequest { + key: None, + limit: 100, + reverse: false, + }; + let pagination = pagination.as_ref().unwrap_or(&default_pagination); + + // range of all denoms after the given key (or until the key for reverse) + let range = match (pagination.reverse, &pagination.key) { + (_, None) => (Bound::Unbounded, Bound::Unbounded), + (true, Some(key)) => (Bound::Unbounded, Bound::Included(key.as_slice())), + (false, Some(key)) => (Bound::Included(key.as_slice()), Bound::Unbounded), + }; + let iter = self.denom_metadata.range::<[u8], _>(range); + // using dynamic dispatch here to reduce code duplication and since this is only testing code + let iter: Box> = if pagination.reverse { + Box::new(iter.rev()) + } else { + Box::new(iter) + }; + + let mut metadata: Vec<_> = iter + // take the requested amount + 1 to get the next key + .take((pagination.limit.saturating_add(1)) as usize) + .map(|(_, m)| m.clone()) + .collect(); + + // if we took more than requested, remove the last element (the next key), + // otherwise this is the last batch + let next_key = if metadata.len() > pagination.limit as usize { + metadata.pop().map(|m| Binary::from(m.base.as_bytes())) + } else { + None + }; + + let metadata_res = AllDenomMetadataResponse { metadata, next_key }; + to_binary(&metadata_res).into() + } }; // system result is always ok in the mock implementation SystemResult::Ok(contract_result) @@ -835,6 +909,8 @@ pub fn mock_wasmd_attr(key: impl Into, value: impl Into) -> Attr #[cfg(test)] mod tests { use super::*; + #[cfg(feature = "cosmwasm_1_3")] + use crate::DenomUnit; use crate::{coin, coins, from_binary, to_binary, ContractInfoResponse, Response}; #[cfg(feature = "staking")] use crate::{Decimal, Delegation}; @@ -1262,6 +1338,96 @@ mod tests { assert_eq!(res.amount, coin(0, "ELF")); } + #[cfg(feature = "cosmwasm_1_3")] + #[test] + fn bank_querier_metadata_works() { + let mut bank = BankQuerier::new(&[]); + bank.set_denom_metadata( + &(0..100) + .map(|i| DenomMetadata { + symbol: format!("FOO{i}"), + name: "Foo".to_string(), + description: "Foo coin".to_string(), + denom_units: vec![DenomUnit { + denom: "ufoo".to_string(), + exponent: 8, + aliases: vec!["microfoo".to_string(), "foobar".to_string()], + }], + display: "FOO".to_string(), + base: format!("ufoo{i}"), + uri: "https://foo.bar".to_string(), + uri_hash: "foo".to_string(), + }) + .collect::>(), + ); + + // querying first 10 should work + let res = bank + .query(&BankQuery::AllDenomMetadata { + pagination: Some(PageRequest { + key: None, + limit: 10, + reverse: false, + }), + }) + .unwrap() + .unwrap(); + let res: AllDenomMetadataResponse = from_binary(&res).unwrap(); + assert_eq!(res.metadata.len(), 10); + assert!(res.next_key.is_some()); + + // querying next 10 should also work + let res2 = bank + .query(&BankQuery::AllDenomMetadata { + pagination: Some(PageRequest { + key: res.next_key, + limit: 10, + reverse: false, + }), + }) + .unwrap() + .unwrap(); + let res2: AllDenomMetadataResponse = from_binary(&res2).unwrap(); + assert_eq!(res2.metadata.len(), 10); + assert_ne!(res.metadata.last(), res2.metadata.first()); + // should have no overlap + for m in res.metadata { + assert!(!res2.metadata.contains(&m)); + } + + // querying all 100 should work + let res = bank + .query(&BankQuery::AllDenomMetadata { + pagination: Some(PageRequest { + key: None, + limit: 100, + reverse: true, + }), + }) + .unwrap() + .unwrap(); + let res: AllDenomMetadataResponse = from_binary(&res).unwrap(); + assert_eq!(res.metadata.len(), 100); + assert!(res.next_key.is_none(), "no more data should be available"); + assert_eq!(res.metadata[0].symbol, "FOO99", "should have been reversed"); + + let more_res = bank + .query(&BankQuery::AllDenomMetadata { + pagination: Some(PageRequest { + key: res.next_key, + limit: u32::MAX, + reverse: true, + }), + }) + .unwrap() + .unwrap(); + let more_res: AllDenomMetadataResponse = from_binary(&more_res).unwrap(); + assert_eq!( + more_res.metadata, res.metadata, + "should be same as previous query" + ); + } + #[cfg(feature = "stargate")] #[test] fn ibc_querier_channel_existing() { diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index e3ba067b6e..7181036cb8 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -20,9 +20,13 @@ use crate::query::{ AllDelegationsResponse, AllValidatorsResponse, BondedDenomResponse, Delegation, DelegationResponse, FullDelegation, StakingQuery, Validator, ValidatorResponse, }; +#[cfg(feature = "cosmwasm_1_3")] +use crate::query::{AllDenomMetadataResponse, DenomMetadataResponse}; use crate::results::{ContractResult, Empty, SystemResult}; use crate::serde::{from_binary, to_binary, to_vec}; use crate::ContractInfoResponse; +#[cfg(feature = "cosmwasm_1_3")] +use crate::{DenomMetadata, PageRequest}; /// Storage provides read and write access to a persistent storage. /// If you only want to provide read access, provide `&Storage` @@ -239,6 +243,28 @@ impl<'a, C: CustomQuery> QuerierWrapper<'a, C> { Ok(res.amount) } + #[cfg(feature = "cosmwasm_1_3")] + pub fn query_denom_metadata(&self, denom: impl Into) -> StdResult { + let request = BankQuery::DenomMetadata { + denom: denom.into(), + } + .into(); + let res: DenomMetadataResponse = self.query(&request)?; + Ok(res.metadata) + } + + #[cfg(feature = "cosmwasm_1_3")] + pub fn query_all_denom_metadata( + &self, + pagination: PageRequest, + ) -> StdResult { + let request = BankQuery::AllDenomMetadata { + pagination: Some(pagination), + } + .into(); + self.query(&request) + } + // this queries another wasm contract. You should know a priori the proper types for T and U // (response and request) based on the contract API pub fn query_wasm_smart( diff --git a/packages/vm/src/testing/instance.rs b/packages/vm/src/testing/instance.rs index 14b85cf7c9..6a4f11a37f 100644 --- a/packages/vm/src/testing/instance.rs +++ b/packages/vm/src/testing/instance.rs @@ -98,7 +98,8 @@ pub struct MockInstanceOptions<'a> { impl MockInstanceOptions<'_> { fn default_capabilities() -> HashSet { #[allow(unused_mut)] - let mut out = capabilities_from_csv("iterator,staking,cosmwasm_1_1,cosmwasm_1_2"); + let mut out = + capabilities_from_csv("iterator,staking,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3"); #[cfg(feature = "stargate")] out.insert("stargate".to_string()); out