feature: query for obtaining amount of vesting coins for all accounts (#2791)

* wip

* Introduced paged queries for getting list of all vesting accounts and for amount of vesting coins

* Added the queries to VestingQueryClient trait

* Added default implementations to all trait queries

* Fixed naming for the vesting coins query

* Helper functions for dealing with paging

* Updated changelog
This commit is contained in:
Jędrzej Stuczyński
2023-01-10 14:58:40 +00:00
committed by GitHub
parent ad8fdbdddf
commit 0df063f9f6
6 changed files with 294 additions and 210 deletions
@@ -12,8 +12,9 @@ use mixnet_contract_common::MixId;
use serde::Deserialize;
use vesting_contract::vesting::Account;
use vesting_contract_common::{
messages::QueryMsg as VestingQueryMsg, AllDelegationsResponse, DelegationTimesResponse,
OriginalVestingResponse, Period, PledgeData, VestingDelegation,
messages::QueryMsg as VestingQueryMsg, AccountVestingCoins, AccountsResponse,
AllDelegationsResponse, BaseVestingAccountInfo, DelegationTimesResponse,
OriginalVestingResponse, Period, PledgeData, VestingCoinsResponse, VestingDelegation,
};
#[async_trait]
@@ -27,74 +28,187 @@ pub trait VestingQueryClient {
.await
}
async fn get_all_accounts_paged(
&self,
start_next_after: Option<String>,
limit: Option<u32>,
) -> Result<AccountsResponse, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetAccountsPaged {
start_next_after,
limit,
})
.await
}
async fn get_all_accounts_vesting_coins_paged(
&self,
start_next_after: Option<String>,
limit: Option<u32>,
) -> Result<VestingCoinsResponse, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetAccountsVestingCoinsPaged {
start_next_after,
limit,
})
.await
}
async fn locked_coins(
&self,
address: &str,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError>;
) -> Result<Coin, NyxdError> {
self.query_vesting_contract::<CosmWasmCoin>(VestingQueryMsg::LockedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
})
.await
.map(Into::into)
}
async fn spendable_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError>;
) -> Result<Coin, NyxdError> {
self.query_vesting_contract::<CosmWasmCoin>(VestingQueryMsg::SpendableCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
})
.await
.map(Into::into)
}
async fn vested_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError>;
) -> Result<Coin, NyxdError> {
self.query_vesting_contract::<CosmWasmCoin>(VestingQueryMsg::GetVestedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
})
.await
.map(Into::into)
}
async fn vesting_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError>;
) -> Result<Coin, NyxdError> {
self.query_vesting_contract::<CosmWasmCoin>(VestingQueryMsg::GetVestingCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
})
.await
.map(Into::into)
}
async fn vesting_start_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NyxdError>;
) -> Result<Timestamp, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetStartTime {
vesting_account_address: vesting_account_address.to_string(),
})
.await
}
async fn vesting_end_time(&self, vesting_account_address: &str)
-> Result<Timestamp, NyxdError>;
async fn vesting_end_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetEndTime {
vesting_account_address: vesting_account_address.to_string(),
})
.await
}
async fn original_vesting(
&self,
vesting_account_address: &str,
) -> Result<OriginalVestingResponse, NyxdError>;
) -> Result<OriginalVestingResponse, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetOriginalVesting {
vesting_account_address: vesting_account_address.to_string(),
})
.await
}
async fn delegated_free(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError>;
) -> Result<Coin, NyxdError> {
self.query_vesting_contract::<CosmWasmCoin>(VestingQueryMsg::GetDelegatedFree {
vesting_account_address: vesting_account_address.to_string(),
block_time,
})
.await
.map(Into::into)
}
/// Returns the total amount of delegated tokens that have vested
async fn delegated_vesting(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError>;
) -> Result<Coin, NyxdError> {
self.query_vesting_contract::<CosmWasmCoin>(VestingQueryMsg::GetDelegatedVesting {
vesting_account_address: vesting_account_address.to_string(),
block_time,
})
.await
.map(Into::into)
}
async fn get_account(&self, address: &str) -> Result<Account, NyxdError>;
async fn get_mixnode_pledge(&self, address: &str) -> Result<Option<PledgeData>, NyxdError>;
async fn get_gateway_pledge(&self, address: &str) -> Result<Option<PledgeData>, NyxdError>;
async fn get_current_vesting_period(
&self,
vesting_account_address: &str,
) -> Result<Period, NyxdError>;
async fn get_account(&self, address: &str) -> Result<Account, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetAccount {
address: address.to_string(),
})
.await
}
async fn get_mixnode_pledge(&self, address: &str) -> Result<Option<PledgeData>, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetMixnode {
address: address.to_string(),
})
.await
}
async fn get_gateway_pledge(&self, address: &str) -> Result<Option<PledgeData>, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetGateway {
address: address.to_string(),
})
.await
}
async fn get_current_vesting_period(&self, address: &str) -> Result<Period, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetCurrentVestingPeriod {
address: address.to_string(),
})
.await
}
async fn get_delegation_timestamps(
&self,
address: &str,
mix_id: MixId,
) -> Result<DelegationTimesResponse, NyxdError>;
) -> Result<DelegationTimesResponse, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetDelegationTimes {
address: address.to_string(),
mix_id,
})
.await
}
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, MixId, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NyxdError>;
) -> Result<AllDelegationsResponse, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetAllDelegations { start_after, limit })
.await
}
async fn get_all_vesting_delegations(&self) -> Result<Vec<VestingDelegation>, NyxdError> {
let mut delegations = Vec::new();
@@ -114,6 +228,44 @@ pub trait VestingQueryClient {
Ok(delegations)
}
async fn get_all_accounts_info(&self) -> Result<Vec<BaseVestingAccountInfo>, NyxdError> {
let mut accounts = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.get_all_accounts_paged(start_after.take(), None)
.await?;
accounts.append(&mut paged_response.accounts);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(accounts)
}
async fn get_all_accounts_vesting_coins(&self) -> Result<Vec<AccountVestingCoins>, NyxdError> {
let mut accounts = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.get_all_accounts_vesting_coins_paged(start_after.take(), None)
.await?;
accounts.append(&mut paged_response.accounts);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res.into_string())
} else {
break;
}
}
Ok(accounts)
}
}
#[async_trait]
@@ -126,188 +278,4 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NyxdClient<C> {
.query_contract_smart(self.vesting_contract_address(), &query)
.await
}
async fn locked_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError> {
let request = VestingQueryMsg::LockedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
async fn spendable_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError> {
let request = VestingQueryMsg::SpendableCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
async fn vested_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError> {
let request = VestingQueryMsg::GetVestedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
async fn vesting_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError> {
let request = VestingQueryMsg::GetVestingCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
async fn vesting_start_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NyxdError> {
let request = VestingQueryMsg::GetStartTime {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
async fn vesting_end_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NyxdError> {
let request = VestingQueryMsg::GetEndTime {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
async fn original_vesting(
&self,
vesting_account_address: &str,
) -> Result<OriginalVestingResponse, NyxdError> {
let request = VestingQueryMsg::GetOriginalVesting {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
async fn delegated_free(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError> {
let request = VestingQueryMsg::GetDelegatedFree {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
/// Returns the total amount of delegated tokens that have vested
async fn delegated_vesting(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NyxdError> {
let request = VestingQueryMsg::GetDelegatedVesting {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
async fn get_account(&self, address: &str) -> Result<Account, NyxdError> {
let request = VestingQueryMsg::GetAccount {
address: address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
async fn get_mixnode_pledge(&self, address: &str) -> Result<Option<PledgeData>, NyxdError> {
let request = VestingQueryMsg::GetMixnode {
address: address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
async fn get_gateway_pledge(&self, address: &str) -> Result<Option<PledgeData>, NyxdError> {
let request = VestingQueryMsg::GetGateway {
address: address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
async fn get_current_vesting_period(&self, address: &str) -> Result<Period, NyxdError> {
let request = VestingQueryMsg::GetCurrentVestingPeriod {
address: address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
async fn get_delegation_timestamps(
&self,
address: &str,
mix_id: MixId,
) -> Result<DelegationTimesResponse, NyxdError> {
let request = VestingQueryMsg::GetDelegationTimes {
address: address.to_string(),
mix_id,
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, MixId, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NyxdError> {
let request = VestingQueryMsg::GetAllDelegations { start_after, limit };
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
}
@@ -134,6 +134,32 @@ pub struct AllDelegationsResponse {
pub start_next_after: Option<(u32, MixId, u64)>,
}
#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
pub struct AccountVestingCoins {
pub account_id: u32,
pub owner: Addr,
pub still_vesting: Coin,
}
#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
pub struct VestingCoinsResponse {
pub accounts: Vec<AccountVestingCoins>,
pub start_next_after: Option<Addr>,
}
#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
pub struct BaseVestingAccountInfo {
pub account_id: u32,
pub owner: Addr,
// TODO: should this particular query/response expose anything else?
}
#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)]
pub struct AccountsResponse {
pub accounts: Vec<BaseVestingAccountInfo>,
pub start_next_after: Option<Addr>,
}
#[cfg(test)]
mod test {
use contracts_common::Percent;
@@ -188,6 +188,14 @@ impl ExecuteMsg {
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetContractVersion {},
GetAccountsPaged {
start_next_after: Option<String>,
limit: Option<u32>,
},
GetAccountsVestingCoinsPaged {
start_next_after: Option<String>,
limit: Option<u32>,
},
LockedCoins {
vesting_account_address: String,
block_time: Option<Timestamp>,
+6
View File
@@ -1,5 +1,11 @@
## Unreleased
### Added
- vesting-contract: `GetAccountsPaged` and `GetAccountsVestingCoinsPaged` queries for querying multiple accounts simultaneously ([#2791])
[#2791]: https://github.com/nymtech/nym/pull/2791
## [nym-contracts-v1.1.2](https://github.com/nymtech/nym/tree/nym-contracts-v1.1.2) (2022-12-07)
### Added
+79 -3
View File
@@ -1,7 +1,7 @@
use crate::errors::ContractError;
use crate::queued_migrations::migrate_to_v2_mixnet_contract;
use crate::storage::{
account_from_address, save_account, BlockTimestampSecs, ADMIN, DELEGATIONS,
account_from_address, save_account, BlockTimestampSecs, ACCOUNTS, ADMIN, DELEGATIONS,
MIXNET_CONTRACT_ADDRESS, MIX_DENOM,
};
use crate::traits::{
@@ -26,8 +26,9 @@ use vesting_contract_common::messages::{
ExecuteMsg, InitMsg, MigrateMsg, QueryMsg, VestingSpecification,
};
use vesting_contract_common::{
AllDelegationsResponse, DelegationTimesResponse, OriginalVestingResponse, Period, PledgeCap,
PledgeData, VestingDelegation,
AccountVestingCoins, AccountsResponse, AllDelegationsResponse, BaseVestingAccountInfo,
DelegationTimesResponse, OriginalVestingResponse, Period, PledgeCap, PledgeData,
VestingCoinsResponse, VestingDelegation,
};
pub const INITIAL_LOCKED_PLEDGE_CAP: Uint128 = Uint128::new(100_000_000_000);
@@ -568,6 +569,19 @@ fn try_create_periodic_vesting_account(
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::GetContractVersion {} => to_binary(&get_contract_version()),
QueryMsg::GetAccountsPaged {
start_next_after,
limit,
} => to_binary(&try_get_all_accounts(deps, start_next_after, limit)?),
QueryMsg::GetAccountsVestingCoinsPaged {
start_next_after,
limit,
} => to_binary(&try_get_all_accounts_locked_coins(
deps,
env,
start_next_after,
limit,
)?),
QueryMsg::LockedCoins {
vesting_account_address,
block_time,
@@ -689,6 +703,68 @@ pub fn get_contract_version() -> ContractBuildInformation {
}
}
pub fn try_get_all_accounts(
deps: Deps<'_>,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<AccountsResponse, ContractError> {
let limit = limit.unwrap_or(150).min(250) as usize;
let start = start_after.map(Bound::exclusive);
let accounts = ACCOUNTS
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| {
res.map(|(_, account)| BaseVestingAccountInfo {
account_id: account.storage_key(),
owner: account.owner_address,
})
})
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = accounts.last().map(|acc| acc.owner.clone());
Ok(AccountsResponse {
accounts,
start_next_after,
})
}
pub fn try_get_all_accounts_locked_coins(
deps: Deps<'_>,
env: Env,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<VestingCoinsResponse, ContractError> {
let limit = limit.unwrap_or(150).min(250) as usize;
let start = start_after.map(Bound::exclusive);
let accounts = ACCOUNTS
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| {
res.map(|(_, account)| {
account
.get_vesting_coins(None, &env, deps.storage)
.map(|still_vesting| AccountVestingCoins {
account_id: account.storage_key(),
owner: account.owner_address,
still_vesting,
})
})
})
.collect::<StdResult<Result<Vec<_>, _>>>()??;
let start_next_after = accounts.last().map(|acc| acc.owner.clone());
Ok(VestingCoinsResponse {
accounts,
start_next_after,
})
}
/// Gets currently locked coins, see [crate::traits::VestingAccount::locked_coins]
pub fn try_get_locked_coins(
vesting_account_address: &str,
+1 -1
View File
@@ -8,7 +8,7 @@ use vesting_contract_common::PledgeData;
pub(crate) type BlockTimestampSecs = u64;
pub const KEY: Item<'_, u32> = Item::new("key");
const ACCOUNTS: Map<'_, String, Account> = Map::new("acc");
pub const ACCOUNTS: Map<'_, String, Account> = Map::new("acc");
// Holds data related to individual accounts
const BALANCES: Map<'_, u32, Uint128> = Map::new("blc");
const WITHDRAWNS: Map<'_, u32, Uint128> = Map::new("wthd");