penumbra_sdk_shielded_pool/component/rpc/
bank_query.rs

1use std::collections::BTreeMap;
2
3use anyhow::Context;
4use async_trait::async_trait;
5use futures::StreamExt;
6use ibc_proto::cosmos::bank::v1beta1::{
7    query_server::Query as BankQuery, QueryAllBalancesRequest, QueryAllBalancesResponse,
8    QueryBalanceRequest, QueryBalanceResponse, QueryParamsRequest, QueryParamsResponse,
9    QueryTotalSupplyRequest, QueryTotalSupplyResponse,
10};
11use ibc_proto::cosmos::bank::v1beta1::{
12    QueryDenomMetadataByQueryStringRequest, QueryDenomMetadataByQueryStringResponse,
13    QueryDenomMetadataRequest, QueryDenomMetadataResponse, QueryDenomOwnersByQueryRequest,
14    QueryDenomOwnersByQueryResponse, QueryDenomOwnersRequest, QueryDenomOwnersResponse,
15    QueryDenomsMetadataRequest, QueryDenomsMetadataResponse, QuerySendEnabledRequest,
16    QuerySendEnabledResponse, QuerySpendableBalanceByDenomRequest,
17    QuerySpendableBalanceByDenomResponse, QuerySpendableBalancesRequest,
18    QuerySpendableBalancesResponse, QuerySupplyOfRequest, QuerySupplyOfResponse,
19};
20use penumbra_sdk_asset::asset::{self, Metadata};
21use penumbra_sdk_ibc::component::state_key as ibc_state_key;
22use penumbra_sdk_num::Amount;
23use penumbra_sdk_proto::StateReadProto as _;
24use tracing::instrument;
25
26use crate::component::AssetRegistryRead as _;
27use crate::state_key;
28
29use super::Server;
30
31#[async_trait]
32impl BankQuery for Server {
33    /// Returns the total supply for all IBC assets.
34    /// Internally-minted assets (Penumbra tokens, LP tokens, delegation tokens, etc.)
35    /// are also included but the supplies are will only reflect what has been transferred out.
36    ///
37    /// TODO: Implement a way to fetch the total supply for these assets.
38    /// TODO: implement pagination
39    #[instrument(skip(self, _request))]
40    async fn total_supply(
41        &self,
42        _request: tonic::Request<QueryTotalSupplyRequest>,
43    ) -> Result<tonic::Response<QueryTotalSupplyResponse>, tonic::Status> {
44        let snapshot = self.storage.latest_snapshot();
45
46        // Find every non-IBC known asset
47        let s = snapshot.prefix(state_key::denom_metadata_by_asset::prefix());
48        let mut total_supply = s
49            .filter_map(move |i: anyhow::Result<(String, Metadata)>| async move {
50                if i.is_err() {
51                    return Some(Err(i.context("bad denom in state").err().unwrap()));
52                }
53                let (_key, denom_metadata) = i.expect("should not be an error");
54
55                // Return a hardcoded 0 supply for now
56                Some(Ok((denom_metadata, Amount::from(0u32))))
57            })
58            .collect::<Vec<_>>()
59            .await
60            .into_iter()
61            .collect::<anyhow::Result<Vec<_>>>()
62            .map_err(|e| tonic::Status::internal(e.to_string()))?
63            .into_iter()
64            .collect::<BTreeMap<_, _>>();
65
66        let s = snapshot.prefix(ibc_state_key::ics20_value_balance::prefix());
67        let ibc_amounts = s
68            .filter_map(move |i: anyhow::Result<(String, Amount)>| async move {
69                if i.is_err() {
70                    return Some(Err(i.context("bad amount in state").err().unwrap()));
71                }
72                let (key, amount) = i.expect("should not be an error");
73
74                // Extract the asset ID from the key
75                let asset_id = key.split('/').last();
76                if asset_id.is_none() {
77                    return Some(Err(asset_id
78                        .context("bad IBC ics20 value balance key in state")
79                        .err()
80                        .unwrap()));
81                }
82                let asset_id = asset_id.expect("should not be an error");
83
84                // Parse the asset ID
85                let asset_id = asset_id.parse::<asset::Id>();
86                if asset_id.is_err() {
87                    return Some(Err(asset_id
88                        .context("invalid IBC ics20 value balance asset ID in state")
89                        .err()
90                        .unwrap()));
91                }
92                let asset_id = asset_id.expect("should not be an error");
93
94                Some(Ok((asset_id, amount)))
95            })
96            .collect::<Vec<_>>()
97            .await
98            .into_iter()
99            .collect::<anyhow::Result<Vec<_>>>()
100            .map_err(|e| tonic::Status::internal(e.to_string()))?;
101
102        // Fetch the denoms associated with the IBC asset IDs
103        for (asset_id, amount) in ibc_amounts {
104            let denom_metadata = snapshot.denom_metadata_by_asset(&asset_id).await;
105            if denom_metadata.is_none() {
106                // This is likely an NFT asset that is intentionally
107                // not registered, so it is fine to exclude from the output.
108                continue;
109            }
110            let denom_metadata = denom_metadata.expect("should not be an error");
111
112            // Add to the total supply seen for this denom.
113            total_supply
114                .entry(denom_metadata)
115                .and_modify(|a| *a += amount)
116                .or_insert(amount);
117        }
118
119        Ok(tonic::Response::new(QueryTotalSupplyResponse {
120            // Pagination disabled for now
121            pagination: None,
122            supply: total_supply
123                .into_iter()
124                .map(
125                    |(denom_metadata, amount)| ibc_proto::cosmos::base::v1beta1::Coin {
126                        denom: denom_metadata.to_string(),
127                        amount: amount.to_string(),
128                    },
129                )
130                .collect::<Vec<ibc_proto::cosmos::base::v1beta1::Coin>>(),
131        }))
132    }
133
134    async fn params(
135        &self,
136        _: tonic::Request<QueryParamsRequest>,
137    ) -> std::result::Result<tonic::Response<QueryParamsResponse>, tonic::Status> {
138        Err(tonic::Status::unimplemented("not implemented"))
139    }
140
141    async fn balance(
142        &self,
143        _: tonic::Request<QueryBalanceRequest>,
144    ) -> std::result::Result<tonic::Response<QueryBalanceResponse>, tonic::Status> {
145        Err(tonic::Status::unimplemented(
146            "not implemented, penumbra is a shielded chain",
147        ))
148    }
149
150    async fn all_balances(
151        &self,
152        _: tonic::Request<QueryAllBalancesRequest>,
153    ) -> std::result::Result<tonic::Response<QueryAllBalancesResponse>, tonic::Status> {
154        Err(tonic::Status::unimplemented(
155            "not implemented, penumbra is a shielded chain",
156        ))
157    }
158
159    async fn spendable_balances(
160        &self,
161        _: tonic::Request<QuerySpendableBalancesRequest>,
162    ) -> std::result::Result<tonic::Response<QuerySpendableBalancesResponse>, tonic::Status> {
163        Err(tonic::Status::unimplemented(
164            "not implemented, penumbra is a shielded chain",
165        ))
166    }
167
168    async fn spendable_balance_by_denom(
169        &self,
170        _: tonic::Request<QuerySpendableBalanceByDenomRequest>,
171    ) -> std::result::Result<tonic::Response<QuerySpendableBalanceByDenomResponse>, tonic::Status>
172    {
173        Err(tonic::Status::unimplemented(
174            "not implemented, penumbra is a shielded chain",
175        ))
176    }
177
178    async fn supply_of(
179        &self,
180        _: tonic::Request<QuerySupplyOfRequest>,
181    ) -> std::result::Result<tonic::Response<QuerySupplyOfResponse>, tonic::Status> {
182        Err(tonic::Status::unimplemented("not implemented"))
183    }
184
185    async fn denom_metadata(
186        &self,
187        _: tonic::Request<QueryDenomMetadataRequest>,
188    ) -> std::result::Result<tonic::Response<QueryDenomMetadataResponse>, tonic::Status> {
189        Err(tonic::Status::unimplemented("not implemented"))
190    }
191
192    async fn denoms_metadata(
193        &self,
194        _: tonic::Request<QueryDenomsMetadataRequest>,
195    ) -> std::result::Result<tonic::Response<QueryDenomsMetadataResponse>, tonic::Status> {
196        Err(tonic::Status::unimplemented("not implemented"))
197    }
198
199    async fn denom_owners(
200        &self,
201        _: tonic::Request<QueryDenomOwnersRequest>,
202    ) -> std::result::Result<tonic::Response<QueryDenomOwnersResponse>, tonic::Status> {
203        Err(tonic::Status::unimplemented("not implemented"))
204    }
205
206    async fn send_enabled(
207        &self,
208        _: tonic::Request<QuerySendEnabledRequest>,
209    ) -> std::result::Result<tonic::Response<QuerySendEnabledResponse>, tonic::Status> {
210        Err(tonic::Status::unimplemented("not implemented"))
211    }
212
213    async fn denom_metadata_by_query_string(
214        &self,
215        _: tonic::Request<QueryDenomMetadataByQueryStringRequest>,
216    ) -> Result<tonic::Response<QueryDenomMetadataByQueryStringResponse>, tonic::Status> {
217        Err(tonic::Status::unimplemented("not implemented"))
218    }
219
220    async fn denom_owners_by_query(
221        &self,
222        _: tonic::Request<QueryDenomOwnersByQueryRequest>,
223    ) -> Result<tonic::Response<QueryDenomOwnersByQueryResponse>, tonic::Status> {
224        Err(tonic::Status::unimplemented("not implemented"))
225    }
226}