fuel_core/schema/
balance.rs

1use crate::{
2    database::database_description::IndexationKind,
3    fuel_core_graphql_api::{
4        api_service::ChainInfoProvider,
5        query_costs,
6    },
7    schema::{
8        ReadViewProvider,
9        scalars::{
10            Address,
11            AssetId,
12            U128,
13        },
14    },
15};
16use anyhow::anyhow;
17use async_graphql::{
18    Context,
19    InputObject,
20    Object,
21    connection::{
22        Connection,
23        EmptyFields,
24    },
25};
26use fuel_core_types::services::graphql_api;
27use futures::StreamExt;
28
29use super::scalars::U64;
30
31pub struct Balance(graphql_api::AddressBalance);
32
33#[Object]
34impl Balance {
35    async fn owner(&self) -> Address {
36        self.0.owner.into()
37    }
38
39    async fn amount(&self) -> U64 {
40        let amount: u64 = self.0.amount.try_into().unwrap_or(u64::MAX);
41        amount.into()
42    }
43
44    async fn amount_u128(&self) -> U128 {
45        self.0.amount.into()
46    }
47
48    async fn asset_id(&self) -> AssetId {
49        self.0.asset_id.into()
50    }
51}
52
53#[derive(InputObject)]
54struct BalanceFilterInput {
55    /// Filter coins based on the `owner` field
56    owner: Address,
57}
58
59#[derive(Default)]
60pub struct BalanceQuery;
61
62#[Object]
63impl BalanceQuery {
64    #[graphql(complexity = "query_costs().balance_query")]
65    async fn balance(
66        &self,
67        ctx: &Context<'_>,
68        #[graphql(desc = "address of the owner")] owner: Address,
69        #[graphql(desc = "asset_id of the coin")] asset_id: AssetId,
70    ) -> async_graphql::Result<Balance> {
71        let query = ctx.read_view()?;
72        let base_asset_id = *ctx
73            .data_unchecked::<ChainInfoProvider>()
74            .current_consensus_params()
75            .base_asset_id();
76        let balance = query
77            .balance(owner.0, asset_id.0, base_asset_id)
78            .await?
79            .into();
80        Ok(balance)
81    }
82
83    // TODO: https://github.com/FuelLabs/fuel-core/issues/2496
84    // This is the complexity we want to use with "balances()" query, but it's not
85    // currently possible, because we need to handle queries with ~10k items.
86    // We use the temporary complexity until the SDKs are updated to not
87    // request such a large number of items.
88    // #[graphql(
89    // complexity = "query_costs().balance_query + query_costs().storage_iterator \
90    // + (query_costs().storage_read * first.unwrap_or_default() as usize) \
91    // + (child_complexity * first.unwrap_or_default() as usize) \
92    // + (query_costs().storage_read * last.unwrap_or_default() as usize) \
93    // + (child_complexity * last.unwrap_or_default() as usize)"
94    // )]
95
96    // The 0.66 factor is a Goldilocks approach to balancing the complexity of the query for the SDKs.
97    // Rust SDK sends a query with child_complexity ≅ 11 and we want to support slightly more
98    // than 10k items in a single query (so we target 11k). The total complexity would be 11k * 11 = 121k,
99    // but since our default limit is 80k, we need the 0.66 factor.
100    // We use the expected cost for the balance_query to differiate between indexation case and non-indexation case.
101    // We assume that the balance_query cost is 0 when the indexation is available.
102    #[graphql(complexity = "if query_costs().balance_query == 0 { \
103        (child_complexity as f32 * first.unwrap_or_default() as f32 * 0.66) as usize + \
104        (child_complexity as f32 * last.unwrap_or_default() as f32 * 0.66) as usize
105    } else { query_costs().balance_query }")]
106    async fn balances(
107        &self,
108        ctx: &Context<'_>,
109        filter: BalanceFilterInput,
110        first: Option<i32>,
111        after: Option<String>,
112        last: Option<i32>,
113        before: Option<String>,
114    ) -> async_graphql::Result<Connection<AssetId, Balance, EmptyFields, EmptyFields>>
115    {
116        let query = ctx.read_view()?;
117        if !query.indexation_flags.contains(&IndexationKind::Balances)
118            && (before.is_some() || after.is_some())
119        {
120            return Err(anyhow!(
121                "Can not use pagination when balances indexation is not available"
122            )
123            .into())
124        }
125        let base_asset_id = *ctx
126            .data_unchecked::<ChainInfoProvider>()
127            .current_consensus_params()
128            .base_asset_id();
129        let owner = filter.owner.into();
130        crate::schema::query_pagination(after, before, first, last, |start, direction| {
131            Ok(query
132                .balances(&owner, (*start).map(Into::into), direction, &base_asset_id)
133                .map(|result| {
134                    result.map(|balance| (balance.asset_id.into(), balance.into()))
135                }))
136        })
137        .await
138    }
139}
140
141impl From<graphql_api::AddressBalance> for Balance {
142    fn from(balance: graphql_api::AddressBalance) -> Self {
143        Balance(balance)
144    }
145}