Skip to content

Commit

Permalink
Merge pull request #1356 from larry0x/larry/bank-query-total-supply
Browse files Browse the repository at this point in the history
Add query for the total supply of a coin
  • Loading branch information
uint authored Aug 29, 2022
2 parents 06d7223 + 492e6d7 commit a9ae6fa
Show file tree
Hide file tree
Showing 17 changed files with 254 additions and 18 deletions.
8 changes: 4 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion contracts/reflect/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
21 changes: 21 additions & 0 deletions contracts/reflect/schema/reflect.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
50 changes: 46 additions & 4 deletions contracts/reflect/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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;

Expand All @@ -56,6 +57,19 @@ pub fn mock_dependencies_with_custom_querier(
}
}

pub fn mock_dependencies_with_custom_querier_and_balances(
balances: &[(&str, &[Coin])],
) -> Backend<MockApi, MockStorage, MockQuerier<SpecialQuery>> {
let custom_querier: MockQuerier<SpecialQuery> = 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, &[]);
Expand Down Expand Up @@ -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...
Expand Down
2 changes: 1 addition & 1 deletion devtools/check_workspace.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion packages/check/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
3 changes: 3 additions & 0 deletions packages/std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
21 changes: 21 additions & 0 deletions packages/std/schema/query_request.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions packages/std/src/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions packages/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
101 changes: 96 additions & 5 deletions packages/std/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -452,7 +455,7 @@ impl<C: DeserializeOwned> MockQuerier<C> {
addr: impl Into<String>,
balance: Vec<Coin>,
) -> Option<Vec<Coin>> {
self.bank.balances.insert(addr.into(), balance)
self.bank.update_balance(addr, balance)
}

#[cfg(feature = "staking")]
Expand Down Expand Up @@ -564,20 +567,68 @@ impl Default for WasmQuerier {

#[derive(Clone, Default)]
pub struct BankQuerier {
#[allow(dead_code)]
/// HashMap<denom, amount>
supplies: HashMap<String, Uint128>,
/// HashMap<address, coins>
balances: HashMap<String, Vec<Coin>>,
}

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<String>,
balance: Vec<Coin>,
) -> Option<Vec<Coin>> {
let result = self.balances.insert(addr.into(), balance);
self.supplies = Self::calculate_supplies(&self.balances);

result
}

fn calculate_supplies(balances: &HashMap<String, Vec<Coin>>) -> HashMap<String, Uint128> {
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<Binary> = 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
Expand Down Expand Up @@ -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");
Expand Down
15 changes: 15 additions & 0 deletions packages/std/src/query/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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 {
Expand Down
Loading

0 comments on commit a9ae6fa

Please sign in to comment.