tycho_common/
storage.rs

1//! Storage traits used by Tycho
2use std::{collections::HashMap, fmt::Display};
3
4use async_trait::async_trait;
5use chrono::NaiveDateTime;
6use thiserror::Error;
7
8use crate::{
9    dto,
10    models::{
11        blockchain::{Block, Transaction},
12        contract::{Account, AccountBalance, AccountDelta},
13        protocol::{
14            ComponentBalance, ProtocolComponent, ProtocolComponentState,
15            ProtocolComponentStateDelta, QualityRange,
16        },
17        token::CurrencyToken,
18        Address, BlockHash, Chain, ComponentId, ContractId, ExtractionState, PaginationParams,
19        ProtocolType, TxHash,
20    },
21    Bytes,
22};
23
24/// Identifies a block in storage.
25#[derive(Debug, Clone, PartialEq, Hash, Eq)]
26pub enum BlockIdentifier {
27    /// Identifies the block by its position on a specified chain.
28    ///
29    /// This form of identification has potential risks as it may become
30    /// ambiguous in certain situations. For example, if the block has not been
31    /// finalised, there exists a possibility of forks occurring. As a result,
32    /// the same number could refer to different blocks on different forks.
33    Number((Chain, i64)),
34
35    /// Identifies a block by its hash.
36    ///
37    /// The hash should be unique across multiple chains. Preferred method if
38    /// the block is very recent.
39    Hash(BlockHash),
40
41    /// Latest stored block for the target chain
42    ///
43    /// Returns the block with the highest block number on the target chain.
44    Latest(Chain),
45}
46
47impl Display for BlockIdentifier {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(f, "{self:?}")
50    }
51}
52
53#[derive(Error, Debug, PartialEq)]
54pub enum StorageError {
55    #[error("Could not find {0} with id `{1}`!")]
56    NotFound(String, String),
57    #[error("The entity {0} with id {1} was already present!")]
58    DuplicateEntry(String, String),
59    #[error("Could not find related {0} for {1} with id `{2}`!")]
60    NoRelatedEntity(String, String, String),
61    #[error("DecodeError: {0}")]
62    DecodeError(String),
63    #[error("Unexpected storage error: {0}")]
64    Unexpected(String),
65    #[error("Currently unsupported operation: {0}")]
66    Unsupported(String),
67    #[error("Write cache unexpectedly dropped notification channel!")]
68    WriteCacheGoneAway(),
69    #[error("Invalid block range encountered")]
70    InvalidBlockRange(),
71}
72
73/// Storage methods for chain specific objects.
74///
75/// This trait abstracts the specific implementation details of a blockchain's
76/// entities, allowing the user to add and retrieve blocks and transactions in a
77/// generic way.
78///
79/// For traceability protocol components and contracts changes are linked to
80/// blocks of their respective chain if applicable. This means while indexing we
81/// need to keep a lightweight and cross chain compatible representation of
82/// blocks and transactions in storage.
83///
84/// It's defined generically over two associated types:
85///
86/// * `Block`: represents a block in the blockchain.
87/// * `Transaction`: represents a transaction within a block.
88#[async_trait]
89pub trait ChainGateway {
90    /// Upserts a new block to the blockchain's storage.
91    ///
92    /// Ignores any existing tx, if the new entry has different attributes
93    /// no error is raised and the old entry is kept.
94    ///
95    /// # Parameters
96    /// - `new`: An instance of `Self::Block`, representing the new block to be stored.
97    ///
98    /// # Returns
99    /// - Empty ok result indicates success. Failure might occur if the block is already present.
100    async fn upsert_block(&self, new: &[Block]) -> Result<(), StorageError>;
101    /// Retrieves a block from storage.
102    ///
103    /// # Parameters
104    /// - `id`: Block's unique identifier of type `BlockIdentifier`.
105    ///
106    /// # Returns
107    /// - An Ok result containing the block. Might fail if the block does not exist yet.
108    async fn get_block(&self, id: &BlockIdentifier) -> Result<Block, StorageError>;
109    /// Upserts a transaction to storage.
110    ///
111    /// Ignores any existing tx, if the new entry has different attributes
112    /// no error is raised and the old entry is kept.
113    ///
114    /// # Parameters
115    /// - `new`: An instance of `Self::Transaction`, representing the new transaction to be stored.
116    ///
117    /// # Returns
118    /// - Empty ok result indicates success. Failure might occur if the
119    /// corresponding block does not exists yet, or if the transaction already
120    /// exists.
121    async fn upsert_tx(&self, new: &[Transaction]) -> Result<(), StorageError>;
122
123    /// Tries to retrieve a transaction from the blockchain's storage using its
124    /// hash.
125    ///
126    /// # Parameters
127    /// - `hash`: The byte slice representing the hash of the transaction to be retrieved.
128    ///
129    /// # Returns
130    /// - An Ok result containing the transaction. Might fail if the transaction does not exist yet.
131    async fn get_tx(&self, hash: &TxHash) -> Result<Transaction, StorageError>;
132
133    /// Reverts the blockchain storage to a previous version.
134    ///
135    /// Reverting state signifies deleting database history. Only the main branch will be kept.
136    ///
137    /// Blocks that are greater than the provided block (`to`) are deleted and any versioned rows
138    /// which were invalidated in the deleted blocks are updated to be valid again.
139    ///
140    /// # Parameters
141    /// - `to` The version to revert to. Given a block uses VersionKind::Last behaviour.
142    /// - `db` The database gateway.
143    ///
144    /// # Returns
145    /// - An Ok if the revert is successful, or a `StorageError` if not.
146    async fn revert_state(&self, to: &BlockIdentifier) -> Result<(), StorageError>;
147}
148
149/// Store and retrieve state of Extractors.
150///
151/// Sometimes extractors may wish to persist their state across restart. E.g.
152/// substreams based extractors need to store the cursor, so they can continue
153/// processing where they left off.
154///
155/// Extractors are uniquely identified by a name and the respective chain which
156/// they are indexing.
157#[async_trait]
158pub trait ExtractionStateGateway {
159    /// Retrieves the state of an extractor instance from a storage.
160    ///
161    /// # Parameters
162    /// - `name` A unique name for the extractor instance.
163    /// - `chain` The chain this extractor is indexing.
164    ///
165    /// # Returns
166    /// Ok if the corrsponding state was retrieved successfully, Err in
167    /// case the state was not found.
168    async fn get_state(&self, name: &str, chain: &Chain) -> Result<ExtractionState, StorageError>;
169
170    /// Saves the state of an extractor instance to a storage.
171    ///
172    /// Creates an entry if not present yet, or updates an already existing
173    /// entry.
174    ///
175    /// # Parameters
176    /// - `state` The state of the extractor that needs to be saved.
177    ///
178    /// # Returns
179    /// Ok, if state was stored successfully, Err if the state is not valid.
180    async fn save_state(&self, state: &ExtractionState) -> Result<(), StorageError>;
181}
182
183/// Point in time as either block or timestamp. If a block is chosen it
184/// timestamp attribute is used.
185#[derive(Debug, Clone, PartialEq, Hash, Eq)]
186pub enum BlockOrTimestamp {
187    Block(BlockIdentifier),
188    Timestamp(NaiveDateTime),
189}
190
191// TODO: remove once deprecated chain field is removed from VersionParam
192#[allow(deprecated)]
193impl TryFrom<&dto::VersionParam> for BlockOrTimestamp {
194    type Error = anyhow::Error;
195
196    fn try_from(version: &dto::VersionParam) -> Result<Self, Self::Error> {
197        match (&version.timestamp, &version.block) {
198            (_, Some(block)) => {
199                // If a full block is provided, we prioritize hash over number and chain
200                let block_identifier = match (&block.hash, &block.chain, &block.number) {
201                    (Some(hash), _, _) => BlockIdentifier::Hash(hash.clone()),
202                    (_, Some(chain), Some(number)) => {
203                        BlockIdentifier::Number((Chain::from(*chain), *number))
204                    }
205                    _ => {
206                        return Err(anyhow::format_err!("Insufficient block information".to_owned()))
207                    }
208                };
209                Ok(BlockOrTimestamp::Block(block_identifier))
210            }
211            (Some(timestamp), None) => Ok(BlockOrTimestamp::Timestamp(*timestamp)),
212            (None, None) => {
213                Err(anyhow::format_err!("Missing timestamp or block identifier".to_owned()))
214            }
215        }
216    }
217}
218
219/// References certain states within a single block.
220///
221/// **Note:** Not all methods that take a version will support all version kinds,
222/// the versions here are included for completeness and to document the
223/// retrieval behaviour that is possible with the storage layout. Please refer
224/// to the individual implementation for information about which version kinds
225/// it supports.
226#[derive(Debug, Clone, Default)]
227pub enum VersionKind {
228    /// Represents the final state within a specific block. Essentially, it
229    /// retrieves the state subsequent to the execution of the last transaction
230    /// executed in that block.
231    #[default]
232    Last,
233    /// Represents the initial state of a specific block. In other words,
234    /// it is the state before any transaction has been executed within that block.
235    First,
236    /// Represents a specific transactions indexed position within a block.
237    /// It includes the state after executing the transaction at that index.
238    Index(i64),
239}
240
241/// A version desribes the state of the DB at a exact point in time.
242/// See the module level docs for more information on how versioning works.
243#[derive(Debug, Clone)]
244pub struct Version(pub BlockOrTimestamp, pub VersionKind);
245
246impl Version {
247    pub fn from_block_number(chain: Chain, number: i64) -> Self {
248        Self(BlockOrTimestamp::Block(BlockIdentifier::Number((chain, number))), VersionKind::Last)
249    }
250    pub fn from_ts(ts: NaiveDateTime) -> Self {
251        Self(BlockOrTimestamp::Timestamp(ts), VersionKind::Last)
252    }
253}
254
255// Helper type to retrieve entities with their total retrievable count.
256#[derive(Debug)]
257pub struct WithTotal<T> {
258    pub entity: T,
259    pub total: Option<i64>,
260}
261
262/// Store and retrieve protocol related structs.
263///
264/// This trait defines how to retrieve protocol components, state as well as
265/// tokens from storage.
266#[async_trait]
267pub trait ProtocolGateway {
268    /// Retrieve ProtocolComponent from the db
269    ///
270    /// # Parameters
271    /// - `chain` The chain of the component
272    /// - `system` Allows to optionally filter by system.
273    /// - `id` Allows to optionally filter by id.
274    ///
275    /// # Returns
276    /// Ok, if found else Err
277    async fn get_protocol_components(
278        &self,
279        chain: &Chain,
280        system: Option<String>,
281        ids: Option<&[&str]>,
282        min_tvl: Option<f64>,
283        pagination_params: Option<&PaginationParams>,
284    ) -> Result<WithTotal<Vec<ProtocolComponent>>, StorageError>;
285
286    /// Retrieves owners of tokens
287    ///
288    /// Queries for owners (protocol components) of tokens that have a certain minimum
289    /// balance and returns a maximum aggregate of those in case there are multiple
290    /// owners.
291    ///
292    /// # Parameters
293    /// - `chain` The chain of the component
294    /// - `tokens` The tokens to query for, any component with at least one of these tokens is
295    ///   returned.
296    /// - `min_balance` A minimum balance we expect the component to have on any of the tokens
297    ///   mentioned in `tokens`.
298    async fn get_token_owners(
299        &self,
300        chain: &Chain,
301        tokens: &[Address],
302        min_balance: Option<f64>,
303    ) -> Result<HashMap<Address, (ComponentId, Bytes)>, StorageError>;
304
305    async fn add_protocol_components(&self, new: &[ProtocolComponent]) -> Result<(), StorageError>;
306
307    async fn delete_protocol_components(
308        &self,
309        to_delete: &[ProtocolComponent],
310        block_ts: NaiveDateTime,
311    ) -> Result<(), StorageError>;
312
313    /// Stores new found ProtocolTypes.
314    ///
315    /// # Parameters
316    /// - `new`  The new protocol types.
317    ///
318    /// # Returns
319    /// Ok if stored successfully.
320    async fn add_protocol_types(
321        &self,
322        new_protocol_types: &[ProtocolType],
323    ) -> Result<(), StorageError>;
324
325    /// Retrieve protocol component states
326    ///
327    /// This resource is versioned, the version can be specified by either block
328    /// or timestamp, for off-chain components, a block version will error.
329    ///
330    /// As the state is retained on a transaction basis on blockchain systems, a
331    /// single version may relate to more than one state. In these cases a
332    /// versioned result is returned, if requesting `Version:All` with the
333    /// latest entry being the state at the end of the block and the first entry
334    /// represents the first change to the state within the block.
335    ///
336    /// # Parameters
337    /// - `chain` The chain of the component
338    /// - `system` The protocol system this component belongs to
339    /// - `ids` The external ids of the components e.g. addresses, or the pairs
340    /// - `at` The version at which the state is valid at.
341    async fn get_protocol_states(
342        &self,
343        chain: &Chain,
344        at: Option<Version>,
345        system: Option<String>,
346        ids: Option<&[&str]>,
347        retrieve_balances: bool,
348        pagination_params: Option<&PaginationParams>,
349    ) -> Result<WithTotal<Vec<ProtocolComponentState>>, StorageError>;
350
351    async fn update_protocol_states(
352        &self,
353        new: &[(TxHash, ProtocolComponentStateDelta)],
354    ) -> Result<(), StorageError>;
355
356    /// Retrieves a tokens from storage
357    ///
358    /// # Parameters
359    /// - `chain` The chain this token is implemented on.
360    /// - `address` The address for the token within the chain.
361    ///
362    /// # Returns
363    /// Ok if the results could be retrieved from the storage, else errors.
364    async fn get_tokens(
365        &self,
366        chain: Chain,
367        address: Option<&[&Address]>,
368        quality: QualityRange,
369        traded_n_days_ago: Option<NaiveDateTime>,
370        pagination_params: Option<&PaginationParams>,
371    ) -> Result<WithTotal<Vec<CurrencyToken>>, StorageError>;
372
373    /// Saves multiple component balances to storage.
374    ///
375    /// # Parameters
376    /// - `component_balances` The component balances to insert.
377    /// - `chain` The chain of the component balances to be inserted.
378    /// - `block_ts` The timestamp of the block that the balances are associated with.
379    ///
380    /// # Return
381    /// Ok if all component balances could be inserted, Err if at least one token failed to
382    /// insert.
383    async fn add_component_balances(
384        &self,
385        component_balances: &[ComponentBalance],
386    ) -> Result<(), StorageError>;
387
388    /// Saves multiple tokens to storage.
389    ///
390    /// Inserts token into storage. Tokens and their properties are assumed to
391    /// be immutable.
392    ///
393    /// # Parameters
394    /// - `token` The tokens to insert.
395    ///
396    /// # Return
397    /// Ok if all tokens could be inserted, Err if at least one token failed to
398    /// insert.
399    async fn add_tokens(&self, tokens: &[CurrencyToken]) -> Result<(), StorageError>;
400
401    /// Updates multiple tokens in storage.
402    ///
403    /// Updates token in storage. Will warn if one of the tokens does not exist in the
404    /// database. Currently assumes that token addresses are unique across chains.
405    /// -
406    ///
407    /// # Parameters
408    /// - `token` The tokens to update.
409    ///
410    /// # Return
411    /// Ok if all tokens could be inserted, Err if at least one token failed to
412    /// insert.
413    async fn update_tokens(&self, tokens: &[CurrencyToken]) -> Result<(), StorageError>;
414
415    /// Retrieve protocol state changes
416    ///
417    /// Fetches all state changes that occurred for the given chain
418    ///
419    /// # Parameters
420    /// - `chain` The chain of the component
421    /// - `start_version` The version at which to start looking for changes at.
422    /// - `end_version` The version at which to stop looking for changes.
423    ///
424    /// # Return
425    /// A list of ProtocolStateDeltas containing all state changes, Err if no changes were found.
426    async fn get_protocol_states_delta(
427        &self,
428        chain: &Chain,
429        start_version: Option<&BlockOrTimestamp>,
430        end_version: &BlockOrTimestamp,
431    ) -> Result<Vec<ProtocolComponentStateDelta>, StorageError>;
432
433    /// Retrieve protocol component balance changes
434    ///
435    /// Fetches all balance changes that occurred for the given protocol system
436    ///
437    /// # Parameters
438    /// - `chain` The chain of the component
439    /// - `start_version` The version at which to start looking for changes at.
440    /// - `target_version` The version at which to stop looking for changes.
441    ///
442    /// # Return
443    /// A vec containing ComponentBalance objects for changed components.
444    async fn get_balance_deltas(
445        &self,
446        chain: &Chain,
447        start_version: Option<&BlockOrTimestamp>,
448        target_version: &BlockOrTimestamp,
449    ) -> Result<Vec<ComponentBalance>, StorageError>;
450
451    async fn get_component_balances(
452        &self,
453        chain: &Chain,
454        ids: Option<&[&str]>,
455        version: Option<&Version>,
456    ) -> Result<HashMap<String, HashMap<Bytes, ComponentBalance>>, StorageError>;
457
458    async fn get_token_prices(&self, chain: &Chain) -> Result<HashMap<Bytes, f64>, StorageError>;
459
460    async fn upsert_component_tvl(
461        &self,
462        chain: &Chain,
463        tvl_values: &HashMap<String, f64>,
464    ) -> Result<(), StorageError>;
465
466    /// Retrieve a list of actively supported protocol systems
467    ///
468    /// Fetches the list of protocol systems supported by the Tycho indexing service.
469    ///
470    /// # Parameters
471    /// - `chain` The chain for which to retrieve supported protocol systems.
472    /// - `pagination_params` Optional pagination parameters to control the number of results.
473    ///
474    /// # Return
475    /// A paginated list of supported protocol systems, along with the total count.
476    async fn get_protocol_systems(
477        &self,
478        chain: &Chain,
479        pagination_params: Option<&PaginationParams>,
480    ) -> Result<WithTotal<Vec<String>>, StorageError>;
481}
482
483/// Manage contracts and their state in storage.
484///
485/// Specifies how to retrieve, add and update contracts in storage.
486#[async_trait]
487pub trait ContractStateGateway {
488    /// Get a contracts state from storage
489    ///
490    /// This method retrieves a single contract from the database.
491    ///
492    /// # Parameters
493    /// - `id` The identifier for the contract.
494    /// - `version` Version at which to retrieve state for. None retrieves the latest state.
495    /// - `include_slots`: Flag to determine whether to include slot changes. If set to `true`, it
496    ///   includes storage slot.
497    /// - `db`: Database session reference.
498    async fn get_contract(
499        &self,
500        id: &ContractId,
501        version: Option<&Version>,
502        include_slots: bool,
503    ) -> Result<Account, StorageError>;
504
505    /// Get multiple contracts' states from storage.
506    ///
507    /// This method retrieves balance and code, and optionally storage, of
508    /// multiple contracts in a chain. It can optionally filter by given
509    /// addresses and retrieve state for specific versions.
510    ///
511    /// # Parameters:
512    /// - `chain`: The blockchain where the contracts reside.
513    /// - `addresses`: Filter for specific addresses. If set to `None`, it retrieves all indexed
514    ///   contracts in the chain.
515    /// - `version`: Version at which to retrieve state for. If set to `None`, it retrieves the
516    ///   latest state.
517    /// - `include_slots`: Flag to determine whether to include slot changes. If set to `true`, it
518    ///   includes storage slot.
519    /// - `db`: Database session reference.
520    ///
521    /// # Returns:
522    /// A `Result` with a list of contract states if the operation is
523    /// successful, or a `StorageError` if the operation fails.
524    async fn get_contracts(
525        &self,
526        chain: &Chain,
527        addresses: Option<&[Address]>,
528        version: Option<&Version>,
529        include_slots: bool,
530        pagination_params: Option<&PaginationParams>,
531    ) -> Result<WithTotal<Vec<Account>>, StorageError>;
532
533    /// Inserts a new contract into the database.
534    ///
535    /// If it the creation transaction is known, the contract will have slots, balance and code
536    /// inserted alongside with the new account else it won't.
537    ///
538    /// # Arguments
539    /// - `new`: A reference to the new contract state to be inserted.
540    /// - `db`: Database session reference.
541    ///
542    /// # Returns
543    /// - A Result with Ok if the operation was successful, and an Err containing `StorageError` if
544    ///   there was an issue inserting the contract into the database. E.g. if the contract already
545    ///   existed.
546    async fn upsert_contract(&self, new: &Account) -> Result<(), StorageError>;
547
548    /// Update multiple contracts
549    ///
550    /// Given contract deltas, this method will batch all updates to contracts across a single
551    /// chain.
552    ///
553    /// As changes are versioned by transaction, each changeset needs to be associated with a
554    /// transaction hash. All references transaction are assumed to be already persisted.
555    ///
556    /// # Arguments
557    ///
558    /// - `chain`: The blockchain which the contracts belong to.
559    /// - `new`: A reference to a slice of tuples where each tuple has a transaction hash (`TxHash`)
560    ///   and a reference to the state delta (`&Self::Delta`) for that transaction.
561    /// - `db`: A mutable reference to the connected database where the updated contracts will be
562    ///   stored.
563    ///
564    /// # Returns
565    ///
566    /// A Result with `Ok` if the operation was successful, and an `Err` containing
567    /// `StorageError` if there was an issue updating the contracts in the database. E.g. if a
568    /// transaction can't be located by it's reference or accounts refer to a different chain then
569    /// the one specified.
570    async fn update_contracts(&self, new: &[(TxHash, AccountDelta)]) -> Result<(), StorageError>;
571
572    /// Mark a contract as deleted
573    ///
574    /// Issues a soft delete of the contract.
575    ///
576    /// # Parameters
577    /// - `id` The identifier for the contract.
578    /// - `at_tx` The transaction hash which deleted the contract. This transaction is assumed to be
579    ///   in storage already. None retrieves the latest state.
580    /// - `db` The database handle or connection.
581    ///
582    /// # Returns
583    /// Ok if the deletion was successful, might Err if:
584    ///  - Contract is not present in storage.
585    ///  - Deletion transaction is not present in storage.
586    ///  - Contract was already deleted.
587    async fn delete_contract(&self, id: &ContractId, at_tx: &TxHash) -> Result<(), StorageError>;
588
589    /// Retrieve a account delta between two versions.
590    ///
591    /// Given start version V1 and end version V2, this method will return the
592    /// changes necessary to move from V1 to V2. So if V1 < V2, it will contain
593    /// the changes of all accounts that changed between the two versions with the
594    /// values corresponding to V2. If V2 < V1 then it will contain all the
595    /// slots that changed between the two versions with the values corresponding to V1.
596    ///
597    /// This method is mainly meant to handle reverts, but can also be used to create delta changes
598    /// between two historical version thus providing the basis for creating a backtestable stream
599    /// of messages.
600    ///
601    /// # Parameters
602    ///
603    /// - `chain` The chain for which to generate the delta changes.
604    /// - `start_version` The deltas start version, given a block uses VersionKind::Last behaviour.
605    ///   If None the latest version is assumed.
606    /// - `end_version` The deltas end version, given a block uses VersionKind::Last behaviour.
607    ///
608    /// # Note
609    ///
610    /// A choice to utilize `BlockOrTimestamp` has been made intentionally in
611    /// this scenario as passing a `Version` by user isn't quite logical.
612    /// Support for deltas is limited to the states at the start or end of
613    /// blocks because blockchain reorganization at the transaction level is not
614    /// common.
615    ///
616    /// The decision to use either the beginning or end state of a block is
617    /// automatically determined by the underlying logic. For example, if we are
618    /// tracing back, `VersionKind::First` retrieval mode will be used.
619    /// Conversely, if we're progressing forward, we would apply the
620    /// `VersionKind::Last` semantics.
621    ///
622    /// # Returns
623    /// A map containing the necessary changes to update a state from start_version to end_version.
624    /// Errors if:
625    ///     - The versions can't be located in storage.
626    ///     - There was an error with the database
627    async fn get_accounts_delta(
628        &self,
629        chain: &Chain,
630        start_version: Option<&BlockOrTimestamp>,
631        end_version: &BlockOrTimestamp,
632    ) -> Result<Vec<AccountDelta>, StorageError>;
633
634    /// Saves multiple account balances to storage.
635    ///
636    /// # Parameters
637    /// - `account_balances` The account balances to insert.
638    /// - `chain` The chain of the account balances to be inserted.
639    /// - `block_ts` The timestamp of the block that the balances are associated with.
640    ///
641    /// # Return
642    /// Ok if all account balances could be inserted, Err if at least one token failed to insert.
643    async fn add_account_balances(
644        &self,
645        account_balances: &[AccountBalance],
646    ) -> Result<(), StorageError>;
647
648    /// Retrieve account balances
649    ///
650    /// # Parameters
651    /// - `chain` The chain of the account balances
652    /// - `accounts` The accounts to query for. If set to `None`, it retrieves balances for all
653    ///   indexed
654    ///  accounts in the chain.
655    /// - `version` Version at which to retrieve balances for. If set to `None`, it retrieves the
656    ///   latest balances.
657    async fn get_account_balances(
658        &self,
659        chain: &Chain,
660        accounts: Option<&[Address]>,
661        version: Option<&Version>,
662    ) -> Result<HashMap<Address, HashMap<Address, AccountBalance>>, StorageError>;
663}
664
665pub trait Gateway:
666    ChainGateway
667    + ContractStateGateway
668    + ExtractionStateGateway
669    + ProtocolGateway
670    + ContractStateGateway
671    + Send
672    + Sync
673{
674}