diff --git a/.circleci/config.yml b/.circleci/config.yml index fd8abd4538..bae052e68f 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_3 + command: cargo build --locked --features abort,iterator,staking,stargate,cosmwasm_1_4 - 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_3 + command: cargo wasm --locked --features abort,iterator,staking,stargate,cosmwasm_1_4 - run: name: Run unit tests (all features) working_directory: ~/project/packages/std - command: cargo test --locked --features abort,iterator,staking,stargate,cosmwasm_1_3 + command: cargo test --locked --features abort,iterator,staking,stargate,cosmwasm_1_4 - 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_3 -- -D warnings + command: cargo clippy --all-targets --features abort,iterator,staking,stargate,cosmwasm_1_4 -- -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_3" + STD=" cargo tarpaulin --skip-clean --out Xml --output-dir reports/std --packages cosmwasm-std --features abort,iterator,staking,stargate,cosmwasm_1_4" 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 3105948a84..a9e08224c1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,8 @@ { - "rust-analyzer.cargo.features": ["abort", "stargate", "staking", "cosmwasm_1_3"] + "rust-analyzer.cargo.features": [ + "abort", + "stargate", + "staking", + "cosmwasm_1_4" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fd9de9720..0f79ddbe7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to ## [Unreleased] +### Added + +- cosmwasm-std: Add + `DistributionQuery::{DelegationRewards, DelegationTotalRewards, DelegatorValidators}`. + This requires the `cosmwasm_1_4` feature to be enabled. ([#1788]) + +[#1788]: https://github.com/CosmWasm/cosmwasm/pull/1788 + ## [1.4.0-beta.1] - 2023-08-29 ### Added diff --git a/docs/CAPABILITIES-BUILT-IN.md b/docs/CAPABILITIES-BUILT-IN.md index 2f2268e4e7..bf31681e9d 100644 --- a/docs/CAPABILITIES-BUILT-IN.md +++ b/docs/CAPABILITIES-BUILT-IN.md @@ -19,3 +19,7 @@ might define others. `BankQuery::DenomMetadata` and `DistributionQuery::DelegatorWithdrawAddress` queries, as well as `DistributionMsg::FundCommunityPool`. Only chains running CosmWasm `1.3.0` or higher support this. +- `cosmwasm_1_4` enables the `DistributionQuery::DelegationRewards`, + `DistributionQuery::DelegationTotalRewards` and + `DistributionQuery::DelegatorValidators` queries. Only chains running CosmWasm + `1.4.0` or higher support this. diff --git a/docs/USING_COSMWASM_STD.md b/docs/USING_COSMWASM_STD.md index 36730f9945..19a04bdb66 100644 --- a/docs/USING_COSMWASM_STD.md +++ b/docs/USING_COSMWASM_STD.md @@ -45,6 +45,7 @@ The libarary comes with the following features: | 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 | +| cosmwasm_1_4 | | Features that require CosmWasm 1.4+ 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 6ba097470f..48998f889f 100644 --- a/packages/check/src/main.rs +++ b/packages/check/src/main.rs @@ -11,7 +11,7 @@ use cosmwasm_vm::capabilities_from_csv; use cosmwasm_vm::internals::{check_wasm, compile, make_compiling_engine}; const DEFAULT_AVAILABLE_CAPABILITIES: &str = - "iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3"; + "iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4"; pub fn main() { let matches = Command::new("Contract checking") diff --git a/packages/go-gen/Cargo.toml b/packages/go-gen/Cargo.toml index 5f3ad6ed74..5b0a24d80e 100644 --- a/packages/go-gen/Cargo.toml +++ b/packages/go-gen/Cargo.toml @@ -9,7 +9,7 @@ publish = false [dependencies] schemars = "0.8.3" -cosmwasm-std = { path = "../std", version = "1.4.0-beta.1", features = ["cosmwasm_1_3", "staking", "stargate", "ibc3"] } +cosmwasm-std = { path = "../std", version = "1.4.0-beta.1", features = ["cosmwasm_1_4", "staking", "stargate", "ibc3"] } cosmwasm-schema = { path = "../schema", version = "1.4.0-beta.1" } anyhow = "1" Inflector = "0.11.4" diff --git a/packages/go-gen/src/main.rs b/packages/go-gen/src/main.rs index 7688db1878..8d3dcc3d40 100644 --- a/packages/go-gen/src/main.rs +++ b/packages/go-gen/src/main.rs @@ -352,6 +352,9 @@ mod tests { // compare_codes!(cosmwasm_std::ValidatorResponse); // does not use "omitempty" for `Validator` field // distribution compare_codes!(cosmwasm_std::DelegatorWithdrawAddressResponse); + compare_codes!(cosmwasm_std::DelegationRewardsResponse); + compare_codes!(cosmwasm_std::DelegationTotalRewardsResponse); + compare_codes!(cosmwasm_std::DelegatorValidatorsResponse); // wasm compare_codes!(cosmwasm_std::ContractInfoResponse); // compare_codes!(cosmwasm_std::CodeInfoResponse); // TODO: Checksum type and "omitempty" diff --git a/packages/go-gen/src/schema.rs b/packages/go-gen/src/schema.rs index 22e6fd7c97..9d9a3fa866 100644 --- a/packages/go-gen/src/schema.rs +++ b/packages/go-gen/src/schema.rs @@ -264,6 +264,7 @@ pub fn custom_type_of(ty: &str) -> Option<&str> { "HexBinary" => Some("Checksum"), "Addr" => Some("string"), "Decimal" => Some("string"), + "Decimal256" => Some("string"), _ => None, } } diff --git a/packages/go-gen/tests/cosmwasm_std__DelegationRewardsResponse.go b/packages/go-gen/tests/cosmwasm_std__DelegationRewardsResponse.go new file mode 100644 index 0000000000..8779f696dc --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__DelegationRewardsResponse.go @@ -0,0 +1,10 @@ +// See +type DelegationRewardsResponse struct { + Rewards []DecCoin `json:"rewards"` +} + +// A coin type with decimal amount. Modeled after the Cosmos SDK's [DecCoin](https://github.com/cosmos/cosmos-sdk/blob/c74e2887b0b73e81d48c2f33e6b1020090089ee0/proto/cosmos/base/v1beta1/coin.proto#L32-L41) type +type DecCoin struct { + Amount string `json:"amount"` + Denom string `json:"denom"` +} \ No newline at end of file diff --git a/packages/go-gen/tests/cosmwasm_std__DelegationTotalRewardsResponse.go b/packages/go-gen/tests/cosmwasm_std__DelegationTotalRewardsResponse.go new file mode 100644 index 0000000000..772861c71b --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__DelegationTotalRewardsResponse.go @@ -0,0 +1,16 @@ +// See +type DelegationTotalRewardsResponse struct { + Rewards []DelegatorReward `json:"rewards"` + Total []DecCoin `json:"total"` +} + +// A coin type with decimal amount. Modeled after the Cosmos SDK's [DecCoin](https://github.com/cosmos/cosmos-sdk/blob/c74e2887b0b73e81d48c2f33e6b1020090089ee0/proto/cosmos/base/v1beta1/coin.proto#L32-L41) type +type DecCoin struct { + Amount string `json:"amount"` + Denom string `json:"denom"` +} + +type DelegatorReward struct { + Reward []DecCoin `json:"reward"` + ValidatorAddress string `json:"validator_address"` +} \ No newline at end of file diff --git a/packages/go-gen/tests/cosmwasm_std__DelegatorValidatorsResponse.go b/packages/go-gen/tests/cosmwasm_std__DelegatorValidatorsResponse.go new file mode 100644 index 0000000000..3b831e04ad --- /dev/null +++ b/packages/go-gen/tests/cosmwasm_std__DelegatorValidatorsResponse.go @@ -0,0 +1,4 @@ +// See +type DelegatorValidatorsResponse struct { + Validators []string `json:"validators"` +} \ No newline at end of file diff --git a/packages/go-gen/tests/cosmwasm_std__DistributionQuery.go b/packages/go-gen/tests/cosmwasm_std__DistributionQuery.go index 79b541a7a1..5d33c3805d 100644 --- a/packages/go-gen/tests/cosmwasm_std__DistributionQuery.go +++ b/packages/go-gen/tests/cosmwasm_std__DistributionQuery.go @@ -1,8 +1,24 @@ - type DelegatorWithdrawAddressQuery struct { DelegatorAddress string `json:"delegator_address"` } +type DelegationRewardsQuery struct { + DelegatorAddress string `json:"delegator_address"` + ValidatorAddress string `json:"validator_address"` +} +type DelegationTotalRewardsQuery struct { + DelegatorAddress string `json:"delegator_address"` +} +type DelegatorValidatorsQuery struct { + DelegatorAddress string `json:"delegator_address"` +} type DistributionQuery struct { + // See DelegatorWithdrawAddress *DelegatorWithdrawAddressQuery `json:"delegator_withdraw_address,omitempty"` + // See + DelegationRewards *DelegationRewardsQuery `json:"delegation_rewards,omitempty"` + // See + DelegationTotalRewards *DelegationTotalRewardsQuery `json:"delegation_total_rewards,omitempty"` + // See + DelegatorValidators *DelegatorValidatorsQuery `json:"delegator_validators,omitempty"` } \ No newline at end of file diff --git a/packages/std/Cargo.toml b/packages/std/Cargo.toml index 8b2487fb7c..b617ef8392 100644 --- a/packages/std/Cargo.toml +++ b/packages/std/Cargo.toml @@ -44,6 +44,8 @@ cosmwasm_1_2 = ["cosmwasm_1_1"] cosmwasm_1_3 = ["cosmwasm_1_2"] # Together with the `iterator` feature this enables additional imports for more # efficient iteration over DB keys or values. +# It also makes `DistributionQuery::{DelegationRewards, DelegationTotalRewards, DelegatorValidators}` +# available for the contract to call. # It requires the host blockchain to run CosmWasm `1.4.0` or higher. cosmwasm_1_4 = ["cosmwasm_1_3"] diff --git a/packages/std/src/exports.rs b/packages/std/src/exports.rs index 473f63a36b..8f057712d4 100644 --- a/packages/std/src/exports.rs +++ b/packages/std/src/exports.rs @@ -53,6 +53,10 @@ extern "C" fn requires_cosmwasm_1_2() -> () {} #[no_mangle] extern "C" fn requires_cosmwasm_1_3() -> () {} +#[cfg(feature = "cosmwasm_1_4")] +#[no_mangle] +extern "C" fn requires_cosmwasm_1_4() -> () {} + /// 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 6bccc14595..99706aa22b 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -69,10 +69,11 @@ pub use crate::pagination::PageRequest; pub use crate::query::{ AllBalanceResponse, AllDelegationsResponse, AllDenomMetadataResponse, AllValidatorsResponse, BalanceResponse, BankQuery, BondedDenomResponse, ChannelResponse, CodeInfoResponse, - ContractInfoResponse, CustomQuery, Delegation, DelegationResponse, - DelegatorWithdrawAddressResponse, DenomMetadataResponse, DistributionQuery, FullDelegation, - IbcQuery, ListChannelsResponse, PortIdResponse, QueryRequest, StakingQuery, SupplyResponse, - Validator, ValidatorResponse, WasmQuery, + ContractInfoResponse, CustomQuery, DecCoin, Delegation, DelegationResponse, + DelegationRewardsResponse, DelegationTotalRewardsResponse, DelegatorReward, + DelegatorValidatorsResponse, DelegatorWithdrawAddressResponse, DenomMetadataResponse, + DistributionQuery, FullDelegation, IbcQuery, ListChannelsResponse, PortIdResponse, + QueryRequest, StakingQuery, SupplyResponse, Validator, ValidatorResponse, WasmQuery, }; #[allow(deprecated)] pub use crate::results::SubMsgExecutionResponse; diff --git a/packages/std/src/query/distribution.rs b/packages/std/src/query/distribution.rs index bd60fa2654..0414ef7927 100644 --- a/packages/std/src/query/distribution.rs +++ b/packages/std/src/query/distribution.rs @@ -9,11 +9,23 @@ use super::query_response::QueryResponseType; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum DistributionQuery { - // https://github.com/cosmos/cosmos-sdk/blob/4f6f6c00021f4b5ee486bbb71ae2071a8ceb47c9/x/distribution/types/query.pb.go#L792-L795 + /// See DelegatorWithdrawAddress { delegator_address: String }, + /// See + #[cfg(feature = "cosmwasm_1_4")] + DelegationRewards { + delegator_address: String, + validator_address: String, + }, + /// See + #[cfg(feature = "cosmwasm_1_4")] + DelegationTotalRewards { delegator_address: String }, + /// See + #[cfg(feature = "cosmwasm_1_4")] + DelegatorValidators { delegator_address: String }, } -// https://github.com/cosmos/cosmos-sdk/blob/4f6f6c00021f4b5ee486bbb71ae2071a8ceb47c9/x/distribution/types/query.pb.go#L832-L835 +/// See #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] #[non_exhaustive] @@ -22,5 +34,73 @@ pub struct DelegatorWithdrawAddressResponse { } impl_response_constructor!(DelegatorWithdrawAddressResponse, withdraw_address: Addr); - impl QueryResponseType for DelegatorWithdrawAddressResponse {} + +/// See +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct DelegationRewardsResponse { + pub rewards: Vec, +} + +impl_response_constructor!(DelegationRewardsResponse, rewards: Vec); +impl QueryResponseType for DelegationRewardsResponse {} + +/// A coin type with decimal amount. +/// Modeled after the Cosmos SDK's [DecCoin] type. +/// However, in contrast to the Cosmos SDK the `amount` string MUST always have a dot at JSON level, +/// see . +/// Also if Cosmos SDK choses to migrate away from fixed point decimals +/// (as shown [here](https://github.com/cosmos/cosmos-sdk/blob/v0.47.4/x/group/internal/math/dec.go#L13-L21 and discussed [here](https://github.com/cosmos/cosmos-sdk/issues/11783)), +/// wasmd needs to truncate the decimal places to 18. +/// +/// [DecCoin]: (https://github.com/cosmos/cosmos-sdk/blob/v0.47.4/proto/cosmos/base/v1beta1/coin.proto#L28-L38) +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct DecCoin { + pub denom: String, + /// An amount in the base denom of the distributed token. + /// + /// Some chains have choosen atto (10^-18) for their token's base denomination. If we used `Decimal` here, we could only store + /// 340282366920938463463.374607431768211455atoken which is 340.28 TOKEN. + pub amount: crate::Decimal256, +} + +impl DecCoin { + pub fn new(amount: crate::Decimal256, denom: impl Into) -> Self { + Self { + denom: denom.into(), + amount, + } + } +} + +/// See +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[non_exhaustive] +pub struct DelegationTotalRewardsResponse { + pub rewards: Vec, + pub total: Vec, +} + +impl_response_constructor!( + DelegationTotalRewardsResponse, + rewards: Vec, + total: Vec +); +impl QueryResponseType for DelegationTotalRewardsResponse {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct DelegatorReward { + pub validator_address: String, + pub reward: Vec, +} + +/// See +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct DelegatorValidatorsResponse { + pub validators: Vec, +} + +impl_response_constructor!(DelegatorValidatorsResponse, validators: Vec); +impl QueryResponseType for DelegatorValidatorsResponse {} diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs index e3b0248cbf..305af4afb1 100644 --- a/packages/std/src/testing/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -5,6 +5,8 @@ use core::ops::Bound; use serde::de::DeserializeOwned; #[cfg(feature = "stargate")] use serde::Serialize; +#[cfg(feature = "cosmwasm_1_3")] +use std::collections::BTreeSet; use std::collections::HashMap; use crate::addresses::{Addr, CanonicalAddr}; @@ -39,12 +41,14 @@ use crate::traits::{Api, Querier, QuerierResult}; use crate::types::{BlockInfo, ContractInfo, Env, MessageInfo, TransactionInfo}; #[cfg(feature = "cosmwasm_1_3")] use crate::{ - query::{AllDenomMetadataResponse, DenomMetadataResponse}, + query::{AllDenomMetadataResponse, DecCoin, DenomMetadataResponse}, PageRequest, }; use crate::{Attribute, DenomMetadata}; #[cfg(feature = "stargate")] use crate::{ChannelResponse, IbcQuery, ListChannelsResponse, PortIdResponse}; +#[cfg(feature = "cosmwasm_1_4")] +use crate::{Decimal256, DelegationRewardsResponse, DelegatorValidatorsResponse}; use super::riffle_shuffle; @@ -935,12 +939,19 @@ impl StakingQuerier { #[derive(Clone, Default)] pub struct DistributionQuerier { withdraw_addresses: HashMap, + /// Mock of accumulated rewards, indexed first by delegator and then validator address. + rewards: BTreeMap>>, + /// Mock of validators that a delegator has bonded to. + validators: BTreeMap>, } #[cfg(feature = "cosmwasm_1_3")] impl DistributionQuerier { pub fn new(withdraw_addresses: HashMap) -> Self { - DistributionQuerier { withdraw_addresses } + DistributionQuerier { + withdraw_addresses, + ..Default::default() + } } pub fn set_withdraw_address( @@ -969,6 +980,31 @@ impl DistributionQuerier { self.withdraw_addresses.clear(); } + /// Sets accumulated rewards for a given validator and delegator pair. + pub fn set_rewards( + &mut self, + validator: impl Into, + delegator: impl Into, + rewards: Vec, + ) { + self.rewards + .entry(delegator.into()) + .or_default() + .insert(validator.into(), rewards); + } + + /// Sets the validators a given delegator has bonded to. + pub fn set_validators( + &mut self, + delegator: impl Into, + validators: impl IntoIterator>, + ) { + self.validators.insert( + delegator.into(), + validators.into_iter().map(Into::into).collect(), + ); + } + pub fn query(&self, request: &DistributionQuery) -> QuerierResult { let contract_result: ContractResult = match request { DistributionQuery::DelegatorWithdrawAddress { delegator_address } => { @@ -981,10 +1017,78 @@ impl DistributionQuerier { }; to_binary(&res).into() } + #[cfg(feature = "cosmwasm_1_4")] + DistributionQuery::DelegationRewards { + delegator_address, + validator_address, + } => { + let res = DelegationRewardsResponse { + rewards: self + .rewards + .get(delegator_address) + .and_then(|v| v.get(validator_address)) + .cloned() + .unwrap_or_default(), + }; + to_binary(&res).into() + } + #[cfg(feature = "cosmwasm_1_4")] + DistributionQuery::DelegationTotalRewards { delegator_address } => { + let validator_rewards = self + .validator_rewards(delegator_address) + .unwrap_or_default(); + let res = crate::DelegationTotalRewardsResponse { + total: validator_rewards + .iter() + .fold(BTreeMap::<&str, DecCoin>::new(), |mut acc, rewards| { + for coin in &rewards.reward { + acc.entry(&coin.denom) + .or_insert_with(|| DecCoin { + denom: coin.denom.clone(), + amount: Decimal256::zero(), + }) + .amount += coin.amount; + } + + acc + }) + .into_values() + .collect(), + rewards: validator_rewards, + }; + to_binary(&res).into() + } + #[cfg(feature = "cosmwasm_1_4")] + DistributionQuery::DelegatorValidators { delegator_address } => { + let res = DelegatorValidatorsResponse { + validators: self + .validators + .get(delegator_address) + .map(|set| set.iter().cloned().collect()) + .unwrap_or_default(), + }; + to_binary(&res).into() + } }; // system result is always ok in the mock implementation SystemResult::Ok(contract_result) } + + /// Helper method to get all rewards for a given delegator. + #[cfg(feature = "cosmwasm_1_4")] + fn validator_rewards(&self, delegator_address: &str) -> Option> { + let validator_rewards = self.rewards.get(delegator_address)?; + + Some( + validator_rewards + .iter() + .map(|(validator, rewards)| crate::DelegatorReward { + validator_address: validator.clone(), + reward: rewards.clone(), + }) + .collect(), + ) + } } pub fn digit_sum(input: &[u8]) -> usize { @@ -1545,6 +1649,104 @@ mod tests { assert_eq!(res.withdraw_address, "addr1"); } + #[cfg(feature = "cosmwasm_1_4")] + #[test] + fn distribution_querier_delegator_validators() { + let mut distribution = DistributionQuerier::default(); + distribution.set_validators("addr0", ["valoper1", "valoper2"]); + + let query = DistributionQuery::DelegatorValidators { + delegator_address: "addr0".to_string(), + }; + + let res = distribution.query(&query).unwrap().unwrap(); + let res: DelegatorValidatorsResponse = from_binary(&res).unwrap(); + assert_eq!(res.validators, ["valoper1", "valoper2"]); + + let query = DistributionQuery::DelegatorValidators { + delegator_address: "addr1".to_string(), + }; + + let res = distribution.query(&query).unwrap().unwrap(); + let res: DelegatorValidatorsResponse = from_binary(&res).unwrap(); + assert_eq!(res.validators, ([] as [String; 0])); + } + + #[cfg(feature = "cosmwasm_1_4")] + #[test] + fn distribution_querier_delegation_rewards() { + use crate::{Decimal256, DelegationTotalRewardsResponse, DelegatorReward}; + + let mut distribution = DistributionQuerier::default(); + let valoper0_rewards = vec![ + DecCoin::new(Decimal256::from_atomics(1234u128, 0).unwrap(), "uatom"), + DecCoin::new(Decimal256::from_atomics(56781234u128, 4).unwrap(), "utest"), + ]; + distribution.set_rewards("valoper0", "addr0", valoper0_rewards.clone()); + + // both exist / are set + let query = DistributionQuery::DelegationRewards { + delegator_address: "addr0".to_string(), + validator_address: "valoper0".to_string(), + }; + let res = distribution.query(&query).unwrap().unwrap(); + let res: DelegationRewardsResponse = from_binary(&res).unwrap(); + assert_eq!(res.rewards, valoper0_rewards); + + // delegator does not exist + let query = DistributionQuery::DelegationRewards { + delegator_address: "nonexistent".to_string(), + validator_address: "valoper0".to_string(), + }; + let res = distribution.query(&query).unwrap().unwrap(); + let res: DelegationRewardsResponse = from_binary(&res).unwrap(); + assert_eq!(res.rewards.len(), 0); + + // validator does not exist + let query = DistributionQuery::DelegationRewards { + delegator_address: "addr0".to_string(), + validator_address: "valopernonexistent".to_string(), + }; + let res = distribution.query(&query).unwrap().unwrap(); + let res: DelegationRewardsResponse = from_binary(&res).unwrap(); + assert_eq!(res.rewards.len(), 0); + + // add one more validator + let valoper1_rewards = vec![DecCoin::new(Decimal256::one(), "uatom")]; + distribution.set_rewards("valoper1", "addr0", valoper1_rewards.clone()); + + // total rewards + let query = DistributionQuery::DelegationTotalRewards { + delegator_address: "addr0".to_string(), + }; + let res = distribution.query(&query).unwrap().unwrap(); + let res: DelegationTotalRewardsResponse = from_binary(&res).unwrap(); + assert_eq!( + res.rewards, + vec![ + DelegatorReward { + validator_address: "valoper0".into(), + reward: valoper0_rewards + }, + DelegatorReward { + validator_address: "valoper1".into(), + reward: valoper1_rewards + }, + ] + ); + assert_eq!( + res.total, + [ + DecCoin::new( + Decimal256::from_atomics(1234u128, 0).unwrap() + Decimal256::one(), + "uatom" + ), + // total for utest should still be the same + DecCoin::new(Decimal256::from_atomics(56781234u128, 4).unwrap(), "utest") + ] + ); + } + #[cfg(feature = "stargate")] #[test] fn ibc_querier_channel_existing() { diff --git a/packages/std/src/traits.rs b/packages/std/src/traits.rs index 2118e189d2..a5402660d1 100644 --- a/packages/std/src/traits.rs +++ b/packages/std/src/traits.rs @@ -322,6 +322,51 @@ impl<'a, C: CustomQuery> QuerierWrapper<'a, C> { self.query(&request) } + #[cfg(feature = "cosmwasm_1_4")] + pub fn query_delegation_rewards( + &self, + delegator: impl Into, + validator: impl Into, + ) -> StdResult> { + use crate::DelegationRewardsResponse; + + let request = DistributionQuery::DelegationRewards { + delegator_address: delegator.into(), + validator_address: validator.into(), + } + .into(); + let DelegationRewardsResponse { rewards } = self.query(&request)?; + + Ok(rewards) + } + + #[cfg(feature = "cosmwasm_1_4")] + pub fn query_delegation_total_rewards( + &self, + delegator: impl Into, + ) -> StdResult { + let request = DistributionQuery::DelegationTotalRewards { + delegator_address: delegator.into(), + } + .into(); + self.query(&request) + } + + #[cfg(feature = "cosmwasm_1_4")] + pub fn query_delegator_validators( + &self, + delegator: impl Into, + ) -> StdResult> { + use crate::DelegatorValidatorsResponse; + + let request = DistributionQuery::DelegatorValidators { + delegator_address: delegator.into(), + } + .into(); + let res: DelegatorValidatorsResponse = self.query(&request)?; + Ok(res.validators) + } + /// 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 0b94321d7e..ce1ba84c07 100644 --- a/packages/vm/src/testing/instance.rs +++ b/packages/vm/src/testing/instance.rs @@ -98,8 +98,9 @@ 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,cosmwasm_1_3"); + let mut out = capabilities_from_csv( + "iterator,staking,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4", + ); #[cfg(feature = "stargate")] out.insert("stargate".to_string()); out